Annotations the SpotBugs tool supports for static analysis control and null safety
—
Track resource creation, cleanup obligations, and lifecycle management for preventing resource leaks. These annotations work with experimental SpotBugs detectors to ensure proper resource handling patterns.
Mark a class or interface as a resource type requiring cleanup.
/**
* Mark a class or interface as a resource type requiring cleanup.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface CleanupObligation {
}Usage Examples:
// Mark classes that require cleanup
@CleanupObligation
public class DatabaseConnection {
private Connection connection;
private boolean closed = false;
public DatabaseConnection(String url) throws SQLException {
this.connection = DriverManager.getConnection(url);
}
// Methods that use the resource
public ResultSet executeQuery(String sql) throws SQLException {
if (closed) {
throw new IllegalStateException("Connection is closed");
}
return connection.createStatement().executeQuery(sql);
}
// Cleanup method that discharges the obligation
@DischargesObligation
public void close() throws SQLException {
if (!closed) {
connection.close();
closed = true;
}
}
}
@CleanupObligation
public interface ManagedResource {
void use();
@DischargesObligation
void cleanup();
}
@CleanupObligation
public class FileHandle implements AutoCloseable {
private FileInputStream stream;
public FileHandle(String filename) throws IOException {
this.stream = new FileInputStream(filename);
}
public int read() throws IOException {
return stream.read();
}
@Override
@DischargesObligation
public void close() throws IOException {
if (stream != null) {
stream.close();
stream = null;
}
}
}Mark a constructor or method as creating a resource which requires cleanup.
/**
* Mark a constructor or method as creating a resource which requires cleanup.
* The marked method must be a member of a class marked with the
* CleanupObligation annotation.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR })
@interface CreatesObligation {
}Usage Examples:
@CleanupObligation
public class ResourceManager {
private List<Resource> resources = new ArrayList<>();
// Constructor creates obligation
@CreatesObligation
public ResourceManager() {
// Initialize resource management
initializeCleanupHooks();
}
// Factory method creates obligation
@CreatesObligation
public static ResourceManager create(String config) {
ResourceManager manager = new ResourceManager();
manager.configure(config);
return manager; // Caller must ensure cleanup
}
// Method that creates obligation
@CreatesObligation
public Resource acquireResource(String resourceId) {
Resource resource = resourcePool.acquire(resourceId);
resources.add(resource);
return resource; // Creates cleanup obligation
}
@DischargesObligation
public void shutdown() {
for (Resource resource : resources) {
resource.release();
}
resources.clear();
}
}
// Usage patterns
public class ResourceClient {
public void useResources() {
// Creating obligation - must ensure cleanup
ResourceManager manager = new ResourceManager(); // @CreatesObligation
try {
Resource resource = manager.acquireResource("db-pool"); // @CreatesObligation
// Use resources
resource.performOperation();
} finally {
// Discharge obligation
manager.shutdown(); // @DischargesObligation
}
}
public void useWithTryWithResources() {
// Better pattern with AutoCloseable
try (ResourceManager manager = ResourceManager.create("config")) {
Resource resource = manager.acquireResource("cache");
resource.performOperation();
} // Automatic cleanup
}
}Mark a method as cleaning up a resource and discharging the cleanup obligation.
/**
* Mark a method as cleaning up a resource. The marked method must be a member
* of a class marked with the CleanupObligation annotation.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface DischargesObligation {
}Usage Examples:
@CleanupObligation
public class NetworkConnection {
private Socket socket;
private boolean connected = false;
@CreatesObligation
public NetworkConnection(String host, int port) throws IOException {
this.socket = new Socket(host, port);
this.connected = true;
}
public void sendData(byte[] data) throws IOException {
if (!connected) {
throw new IllegalStateException("Connection closed");
}
socket.getOutputStream().write(data);
}
// Primary cleanup method
@DischargesObligation
public void disconnect() throws IOException {
if (connected) {
socket.close();
connected = false;
}
}
// Alternative cleanup method
@DischargesObligation
public void forceClose() {
if (connected) {
try {
socket.close();
} catch (IOException e) {
// Log but don't throw - this is force close
logger.warn("Error during force close", e);
}
connected = false;
}
}
// Finalizer as last resort (not recommended but shows pattern)
@DischargesObligation
@Override
protected void finalize() throws Throwable {
try {
if (connected) {
forceClose();
}
} finally {
super.finalize();
}
}
}
// Multiple cleanup methods example
@CleanupObligation
public class DatabaseTransaction {
private Connection connection;
private boolean active = true;
@CreatesObligation
public DatabaseTransaction(Connection connection) {
this.connection = connection;
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
throw new RuntimeException("Failed to start transaction", e);
}
}
// Successful completion discharges obligation
@DischargesObligation
public void commit() throws SQLException {
if (active) {
connection.commit();
connection.setAutoCommit(true);
active = false;
}
}
// Unsuccessful completion also discharges obligation
@DischargesObligation
public void rollback() throws SQLException {
if (active) {
connection.rollback();
connection.setAutoCommit(true);
active = false;
}
}
}Indicate that an overriding method must invoke the overridden method.
/**
* Indicate that an overriding method must invoke the overridden method.
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
@interface OverrideMustInvoke {
/**
* When the superclass method should be invoked
*/
When value() default When.ANYTIME;
}Usage Examples:
public abstract class BaseResource {
// Subclasses must call super.initialize()
@OverrideMustInvoke(When.FIRST)
protected void initialize() {
// Base initialization that must happen first
setupLogging();
validateConfiguration();
}
// Subclasses must call super.cleanup()
@OverrideMustInvoke(When.LAST)
protected void cleanup() {
// Base cleanup that must happen last
closeConnections();
releaseResources();
}
// Subclasses must call super method at some point
@OverrideMustInvoke(When.ANYTIME)
protected void processData(String data) {
// Common processing logic
validateData(data);
logProcessing(data);
}
}
public class DatabaseResource extends BaseResource {
@Override
protected void initialize() {
// MUST call super.initialize() first due to When.FIRST
super.initialize();
// Subclass-specific initialization
connectToDatabase();
createTables();
}
@Override
protected void cleanup() {
// Subclass-specific cleanup first
closeDatabaseConnections();
dropTemporaryTables();
// MUST call super.cleanup() last due to When.LAST
super.cleanup();
}
@Override
protected void processData(String data) {
// Can call super method at any point (When.ANYTIME)
preprocessData(data);
super.processData(data); // Call at any point
postprocessData(data);
}
}
// Usage with resource management
@CleanupObligation
public abstract class ManagedService {
@CreatesObligation
public ManagedService() {
initialize();
}
@OverrideMustInvoke(When.FIRST)
protected void initialize() {
// Base service initialization
startMetrics();
registerShutdownHook();
}
@OverrideMustInvoke(When.LAST)
@DischargesObligation
public void shutdown() {
// Base service shutdown
stopMetrics();
unregisterShutdownHook();
}
}Specifies when a method should be invoked during override.
/**
* @deprecated Legacy enum for method invocation timing
*/
@Deprecated
enum When {
/** Method should be invoked first, before subclass logic */
FIRST,
/** Method can be invoked at any time during subclass execution */
ANYTIME,
/** Method should be invoked last, after subclass logic */
LAST
}@CleanupObligation
public class AutoCloseableResource implements AutoCloseable {
@CreatesObligation
public AutoCloseableResource(String resourceId) {
// Acquire resource
}
@Override
@DischargesObligation
public void close() {
// Release resource
}
}
// Usage with automatic cleanup
public void processWithAutoClose() {
try (AutoCloseableResource resource = new AutoCloseableResource("id")) {
resource.performOperation();
} // Automatic cleanup via close()
}@CleanupObligation
public class ConnectionPool {
@CreatesObligation
public static ConnectionPool create(int maxConnections) {
return new ConnectionPool(maxConnections);
}
@CreatesObligation
public Connection borrowConnection() {
return connectionQueue.take(); // Creates obligation to return
}
@DischargesObligation
public void returnConnection(Connection connection) {
connectionQueue.offer(connection); // Discharges borrow obligation
}
@DischargesObligation
public void shutdown() {
// Close all pooled connections
connectionQueue.forEach(Connection::close);
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-github-spotbugs--spotbugs-annotations