CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-github-spotbugs--spotbugs-annotations

Annotations the SpotBugs tool supports for static analysis control and null safety

Pending
Overview
Eval results
Files

resource-management.mddocs/

Resource Management

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.

Capabilities

CleanupObligation Annotation

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;
        }
    }
}

CreatesObligation Annotation

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
    }
}

DischargesObligation Annotation

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;
        }
    }
}

OverrideMustInvoke Annotation

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();
    }
}

When Enum (Deprecated)

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
}

Resource Management Patterns

Try-With-Resources Integration

@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()
}

Resource Pools

@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);
    }
}

Best Practices

  1. Mark resource types: Apply @CleanupObligation to all classes that manage resources
  2. Mark creation points: Use @CreatesObligation on constructors and factory methods
  3. Mark cleanup methods: Use @DischargesObligation on all methods that release resources
  4. Integrate with AutoCloseable: Implement AutoCloseable when possible for automatic cleanup
  5. Use try-with-resources: Prefer try-with-resources for automatic resource management
  6. Handle exceptions: Ensure cleanup methods handle exceptions gracefully
  7. Document lifecycle: Clearly document the expected resource lifecycle
  8. Use override annotations: Apply @OverrideMustInvoke to ensure proper inheritance patterns

Install with Tessl CLI

npx tessl i tessl/maven-com-github-spotbugs--spotbugs-annotations

docs

default-annotations.md

index.md

null-safety.md

resource-management.md

return-value-checking.md

testing-annotations.md

warning-suppression.md

tile.json