Annotations the SpotBugs tool supports for static analysis control and null safety
—
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.
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
}
}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");
}
}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
}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);
}
}@CheckReturnValue(explanation = "HTTP status must be verified")
public int makeHttpRequest(@NonNull String url) {
// Returns HTTP status code
return httpClient.get(url).getStatusCode();
}@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;
}
}@CheckReturnValue(explanation = "Validation errors must be handled")
public boolean isValidEmail(@NonNull String email) {
return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}@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;
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-github-spotbugs--spotbugs-annotations