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
Track up to 100 issues per month on the free plan; events are not billed.
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 moreSvelteKit Logging: Server Hooks and Client Errors
SvelteKit logging guide: structured server logs in hooks.server.ts, request IDs and timing, client error capture in hooks.client.ts, with code.
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 moreFrom the same team
SEOBolt
Technical SEO and AI search audits with ready-to-apply fix plans.
Try SEOBolt →GrowFast
Domain in, qualified B2B leads out, with cold outreach and deliverability handled.
Try GrowFast →AutoShort
Turn a topic into a faceless short: AI script, voiceover and captions.
Try AutoShort →RemixViral
Paste a long video and get back the moments worth posting, cut vertical and captioned.
Try RemixViral →