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

return-value-checking.mddocs/

Return Value Checking

Enforce that method return values are checked by callers to prevent ignored error conditions and resource leaks. This is particularly important for methods that return status codes, validation results, or resource handles.

Capabilities

CheckReturnValue Annotation

Marks methods whose return values should always be checked when invoking the method.

/**
 * This annotation is used to denote a method whose return value should always
 * be checked when invoking the method.
 *
 * The checker treats this annotation as inherited by overriding methods.
 */
@Documented
@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR })
@Retention(RetentionPolicy.CLASS)
@interface CheckReturnValue {

    /**
     * @deprecated Use confidence() instead
     */
    @Deprecated
    Priority priority() default Priority.MEDIUM;

    /**
     * The confidence level for this check
     */
    Confidence confidence() default Confidence.MEDIUM;

    /**
     * A textual explanation of why the return value should be checked
     */
    String explanation() default "";
}

Usage Examples:

public class ConnectionManager {
    
    // Return value must be checked - connection might fail
    @CheckReturnValue(explanation = "Connection status must be verified before use")
    public boolean connect() {
        try {
            establishConnection();
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    
    // File operations should check success status
    @CheckReturnValue(explanation = "File operations may fail and should be verified")
    public boolean saveToFile(@NonNull String filename, @NonNull String data) {
        try {
            Files.write(Paths.get(filename), data.getBytes());
            return true;
        } catch (IOException e) {
            logger.error("Failed to save file: " + filename, e);
            return false;
        }
    }
    
    // Resource creation must be checked
    @CheckReturnValue(explanation = "Resource acquisition may fail")
    public InputStream openResource(@NonNull String resourcePath) {
        return getClass().getResourceAsStream(resourcePath); // May return null
    }
    
    // Validation results must be examined
    @CheckReturnValue(explanation = "Validation errors must be handled")
    public ValidationResult validateInput(@NonNull String input) {
        return validator.validate(input);
    }
}

// Proper usage - checking return values
public class ServiceClient {
    
    public void performOperation() {
        ConnectionManager manager = new ConnectionManager();
        
        // GOOD: Check return value
        boolean connected = manager.connect();
        if (!connected) {
            throw new RuntimeException("Failed to connect");
        }
        
        // GOOD: Check file operation result
        boolean saved = manager.saveToFile("output.txt", "data");
        if (!saved) {
            logger.warn("Failed to save output file");
        }
        
        // GOOD: Check resource acquisition
        InputStream stream = manager.openResource("/config.properties");
        if (stream == null) {
            throw new RuntimeException("Configuration file not found");
        }
        
        // GOOD: Handle validation results
        ValidationResult result = manager.validateInput(userInput);
        if (!result.isValid()) {
            displayErrors(result.getErrors());
            return;
        }
    }
    
    public void badUsage() {
        ConnectionManager manager = new ConnectionManager();
        
        // BAD: Ignoring return value - SpotBugs will warn
        manager.connect(); // Warning: return value should be checked
        
        // BAD: Not checking file operation
        manager.saveToFile("output.txt", "data"); // Warning: return value ignored
    }
}

Constructor Return Value Checking

The annotation can also be applied to constructors to indicate that construction success should be verified.

public class ResourceHandle {
    
    // Constructor that may fail - check for null or use factory method
    @CheckReturnValue(explanation = "Resource creation may fail")
    public ResourceHandle(@NonNull String resourceId) {
        if (!isValidResource(resourceId)) {
            throw new IllegalArgumentException("Invalid resource: " + resourceId);
        }
        // Initialize resource
    }
    
    // Factory method with return value checking
    @CheckReturnValue(explanation = "Resource creation may fail, returns null on failure")
    public static ResourceHandle tryCreate(@NonNull String resourceId) {
        try {
            return new ResourceHandle(resourceId);
        } catch (Exception e) {
            return null; // Caller must check for null
        }
    }
}

// Usage
public void createResource() {
    // GOOD: Using exception-based constructor (automatic checking)
    try {
        ResourceHandle handle = new ResourceHandle("resource-123");
        useResource(handle);
    } catch (IllegalArgumentException e) {
        logger.error("Failed to create resource", e);
    }
    
    // GOOD: Checking factory method return value
    ResourceHandle handle = ResourceHandle.tryCreate("resource-456");
    if (handle != null) {
        useResource(handle);
    } else {
        logger.error("Failed to create resource");
    }
}

Method Overriding and Inheritance

The @CheckReturnValue annotation is inherited by overriding methods.

public abstract class BaseService {
    
    @CheckReturnValue(explanation = "Service operations may fail")
    public abstract boolean performOperation();
}

public class DatabaseService extends BaseService {
    
    // Inherits @CheckReturnValue from parent method
    @Override
    public boolean performOperation() {
        try {
            executeQuery();
            return true;
        } catch (SQLException e) {
            return false;
        }
    }
}

// Usage - must check return value even when calling through subclass
public void useService() {
    DatabaseService service = new DatabaseService();
    
    // GOOD: Checking inherited return value requirement
    boolean success = service.performOperation();
    if (!success) {
        handleFailure();
    }
    
    // BAD: Ignoring return value - SpotBugs will warn
    service.performOperation(); // Warning: return value should be checked
}

Integration with Confidence Levels

Use confidence levels to indicate how important return value checking is.

public class SecurityService {
    
    // High confidence - critical security operation
    @CheckReturnValue(
        confidence = Confidence.HIGH,
        explanation = "Authentication failures must be handled"
    )
    public boolean authenticate(@NonNull String username, @NonNull String password) {
        return authProvider.validate(username, password);
    }
    
    // Medium confidence - important but not critical
    @CheckReturnValue(
        confidence = Confidence.MEDIUM,
        explanation = "Cache operations should be verified"
    )
    public boolean cacheData(@NonNull String key, @NonNull Object data) {
        return cache.put(key, data);
    }
    
    // Low confidence - nice to check but not essential
    @CheckReturnValue(
        confidence = Confidence.LOW,
        explanation = "Logging failures are typically non-critical"
    )
    public boolean logEvent(@NonNull String event) {
        return logger.log(event);
    }
}

Common Use Cases

Status Code Returns

@CheckReturnValue(explanation = "HTTP status must be verified")
public int makeHttpRequest(@NonNull String url) {
    // Returns HTTP status code
    return httpClient.get(url).getStatusCode();
}

Resource Acquisition

@CheckReturnValue(explanation = "File handle may be null if file doesn't exist")
public FileInputStream openFile(@NonNull String filename) {
    try {
        return new FileInputStream(filename);
    } catch (FileNotFoundException e) {
        return null;
    }
}

Validation Results

@CheckReturnValue(explanation = "Validation errors must be handled")
public boolean isValidEmail(@NonNull String email) {
    return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}

Lock Acquisition

@CheckReturnValue(explanation = "Lock acquisition may fail or timeout")
public boolean tryLock(long timeout, TimeUnit unit) {
    try {
        return lock.tryLock(timeout, unit);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return false;
    }
}

Best Practices

  1. Use meaningful explanations: Provide clear reasons why the return value should be checked
  2. Apply to failure-prone operations: Focus on methods that can fail or return error indicators
  3. Consider inheritance: Remember that annotations are inherited by overriding methods
  4. Use appropriate confidence levels: Match confidence to the criticality of checking the return value
  5. Document alternatives: If return value checking isn't possible, document exception-based alternatives
  6. Consistent application: Apply consistently across similar methods in your API

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