Error Handling like a master
- Mark Kendall
- 5 days ago
- 3 min read
To do really good error checking — whether you’re using Java, Spring Boot, or any modern programming language — you need a comprehensive, layered approach.
It’s not just about try-catch or logging. The best systems combine validation, structured exception handling, centralized error processing, observability, and recovery mechanisms.
⸻
✅ THE BEST WAY TO DO ERROR CHECKING (GENERALIZED + SPRING BOOT FOCUSED)
⸻
🔹 1. Prevent Errors Early (Validation First)
Avoid problems by validating inputs and states before they cause errors.
• In Spring Boot:
• Use @Valid and Bean Validation (javax.validation.constraints) in your DTOs and method parameters.
• Example:
public class UserDTO {
@NotNull
private String email;
}
@PostMapping("/register")
public ResponseEntity<?> register(@Valid @RequestBody UserDTO dto) {
...
}
• At service or domain level, validate invariants before doing anything critical.
⸻
🔹 2. Throw Specific Exceptions
Use meaningful, custom exceptions to represent real problems in your domain.
• Avoid generic exceptions like RuntimeException unless you’re rethrowing or wrapping.
• Example:
if (!userExists) {
throw new UserNotFoundException("User not found for id " + id);
}
• Bonus: include enough context in the exception (e.g., user ID, payload) but not PII.
⸻
🔹 3. Use try-catch Where It Matters
Don’t catch everything. Only catch exceptions you can handle.
• Example:
try {
paymentService.chargeCard(cardDetails);
} catch (CardDeclinedException e) {
log.warn("Card was declined for user {}", userId);
return ResponseEntity.status(402).body("Card declined");
} catch (Exception e) {
log.error("Unexpected error during payment", e);
throw new PaymentProcessingException("Unknown payment error");
}
✅ Tip: Let Spring handle what you can’t — don’t catch just to suppress or log silently.
⸻
🔹 4. Centralize Error Handling with @ControllerAdvice
Catch everything once, centrally, and return appropriate HTTP responses or fallback behavior.
• Example:
@RestControllerAdvice
public class ErrorHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<?> handleNotFound(UserNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", ex.getMessage()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleGeneric(Exception ex) {
log.error("Unhandled error", ex);
return ResponseEntity.status(500).body("Internal server error");
}
}
⸻
🔹 5. Log Strategically and Structurally
Good logging ≠ lots of logging. Log with structure and purpose.
• Use:
• INFO for major actions
• WARN for recoverable problems
• ERROR for things that require attention
• Include:
• Correlation/request IDs
• Contextual info (not sensitive data)
• Stack traces only when useful
• Example:
log.warn("Invalid payment attempt by user {} with card ending in {}", userId, lastFour);
⸻
🔹 6. Observability: Metrics + Tracing
Hook into monitoring systems for early warning and traceability.
• Tools:
• Spring Boot Actuator
• Micrometer + Prometheus/Grafana
• OpenTelemetry (tracing exceptions across services)
⸻
🔹 7. Resilience & Graceful Degradation
• Use libraries like:
• Resilience4j for retries, circuit breakers, rate limiting
• Spring Retry for automatic retry on transient failures
@Retryable(value = RemoteServiceException.class, maxAttempts = 3)
public void callRemoteService() { ... }
⸻
TL;DR: Cheat Sheet for Robust Error Handling
Layer Tool/Approach Purpose
Input validation @Valid, custom checks Prevent garbage in
Specific exceptions Custom extends RuntimeException Clear semantics and traceability
Smart try/catch Catch what you can recover from Avoid unnecessary noise
Central handling @ControllerAdvice DRY and consistent error responses
Structured logging SLF4J + MDC Troubleshoot and correlate
Observability Metrics + Tracing Detect problems before users do
Resilience Retry, CircuitBreaker Keep system running despite failures
⸻
If you’re doing all the above, you’re not just doing “error checking” — you’re engineering resilience, clarity, and maintainability.
Want an example repo or code structure for all this bundled together?
Comments