Java Error Handling Patterns for Modern Applications
Java's checked exception system is powerful but often misused. Here are patterns that keep error handling clean and maintainable.
Custom Exception Hierarchy
public abstract class AppException extends RuntimeException {
private final String errorCode;
private final HttpStatus status;
protected AppException(String message, String errorCode, HttpStatus status) {
super(message);
this.errorCode = errorCode;
this.status = status;
}
}
public class ResourceNotFoundException extends AppException {
public ResourceNotFoundException(String resource, String id) {
super(String.format("%s with id %s not found", resource, id),
"RESOURCE_NOT_FOUND", HttpStatus.NOT_FOUND);
}
}
public class ValidationException extends AppException {
private final List<FieldError> fieldErrors;
public ValidationException(List<FieldError> errors) {
super("Validation failed", "VALIDATION_ERROR", HttpStatus.BAD_REQUEST);
this.fieldErrors = errors;
}
}Global Exception Handler (Spring Boot)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(AppException.class)
public ResponseEntity<ErrorResponse> handleAppException(AppException ex) {
ErrorResponse response = new ErrorResponse(
ex.getErrorCode(), ex.getMessage()
);
return new ResponseEntity<>(response, ex.getStatus());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleUnexpected(Exception ex) {
log.error("Unexpected error", ex);
return ResponseEntity.status(500)
.body(new ErrorResponse("INTERNAL_ERROR", "An unexpected error occurred"));
}
}Use Optional Instead of Null Returns
public Optional<User> findUser(String id) {
return userRepository.findById(id);
}
// Caller
User user = userService.findUser(id)
.orElseThrow(() -> new ResourceNotFoundException("User", id));Best Practices
- Use unchecked exceptions for business logic — checked exceptions clutter APIs
- Include error codes — machine-readable codes are easier to handle than messages
- Don't catch Exception broadly — catch specific types
- Log at the boundary — log once at the top-level handler, not at every layer
- Never swallow exceptions —
catch (Exception e) {}hides bugs
Integrate Bugsly to catch the unexpected exceptions that slip through your handler. The ones you didn't anticipate are the ones that matter most.
Try Bugsly Free
AI-powered error tracking that explains your bugs. Set up in 2 minutes, free forever for small projects.
Get Started FreeRelated Articles
Laravel Logging Best Practices
Set up production-ready logging in Laravel with channels, contextual data, log rotation, and integration with monitoring services.
Read moreSvelte Logging Best Practices
Implement production-ready logging in Svelte and SvelteKit applications with server hooks, client error capture, and structured log formatting.
Read moreFlask Production Best Practices
Essential Flask best practices for production including application factories, blueprints, configuration management, and error handling patterns.
Read moreLaravel Error Handling Patterns
Master Laravel error handling with custom exceptions, the Handler class, renderable exceptions, and API error response formatting.
Read more