# Techniques for Robust Error Handling in Spring Boot Microservices

<div><strong>Source</strong>: <a href='https://www.tuanh.net/blog/spring/techniques-for-robust-error-handling-in-spring-boot-microservices'>Techniques for Robust Error Handling in Spring Boot Microservices</a></div><div class="line-post"> 
 <h2 class="title-post">1. Centralizing Error Handling with @ControllerAdvice</h2> 
</div>
<div class="line-post">
  Centralizing exception handling is fundamental in Spring Boot microservices. By using @ControllerAdvice, we can manage all exceptions from a single location, improving code maintainability and enforcing consistent error responses across microservices. 
</div>
<div class="line-post"> 
 <h3 class="sub1">1.1 Setting up a Global Exception Handler</h3> 
</div>
<div class="line-post">
  To create a central error-handling mechanism, we’ll define a GlobalExceptionHandler class annotated with @RestControllerAdvice. This class will catch exceptions across controllers, format them, and return appropriate HTTP responses to the client. 
</div>
<pre class="code">@RestControllerAdvice<br><span class="keyword-css">public</span> <span class="keyword-css">class</span> GlobalExceptionHandler {<br><br>    @ExceptionHandler(ResourceNotFoundException.<span class="keyword-css">class</span>)<br>    <span class="keyword-css">public</span> ResponseEntity&lt;ErrorResponse&gt; handleResourceNotFound(ResourceNotFoundException ex) {<br>        ErrorResponse errorResponse = <span class="keyword-css">new</span> ErrorResponse("RESOURCE_NOT_FOUND", ex.getMessage());<br>        <span class="keyword-css">return</span> <span class="keyword-css">new</span> ResponseEntity&lt;&gt;(errorResponse, HttpStatus.NOT_FOUND);<br>    }<br><br>    @ExceptionHandler(BadRequestException.<span class="keyword-css">class</span>)<br>    <span class="keyword-css">public</span> ResponseEntity&lt;ErrorResponse&gt; handleBadRequest(BadRequestException ex) {<br>        ErrorResponse errorResponse = <span class="keyword-css">new</span> ErrorResponse("BAD_REQUEST", ex.getMessage());<br>        <span class="keyword-css">return</span> <span class="keyword-css">new</span> ResponseEntity&lt;&gt;(errorResponse, HttpStatus.BAD_REQUEST);<br>    }<br><br>    @ExceptionHandler(Exception.<span class="keyword-css">class</span>)<br>    <span class="keyword-css">public</span> ResponseEntity&lt;ErrorResponse&gt; handleGenericException(Exception ex) {<br>        ErrorResponse errorResponse = <span class="keyword-css">new</span> ErrorResponse("INTERNAL_SERVER_ERROR", "An unexpected error occurred.");<br>        <span class="keyword-css">return</span> <span class="keyword-css">new</span> ResponseEntity&lt;&gt;(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);<br>    }<br>}</pre>
<div class="line-post"> <strong>Explanation:</strong> 
</div>
<div class="line-post"> 
 <ul> 
  <li><strong>Custom Error Messages</strong>: Each error has a specific ErrorResponse object, ensuring the client receives a clear and consistent error message.</li> 
  <li><strong>Granular Error Types</strong>: Different handlers for exceptions like ResourceNotFoundException and BadRequestException ensure that each error type has an appropriate HTTP status code and message.</li> 
  <li><strong>Fallback for Unhandled Exceptions</strong>: A generic handler catches unexpected errors, preserving system stability by hiding internal details and returning a user-friendly message.</li> 
 </ul> 
</div>
<div class="line-post"> 
 <h3 class="sub1">1.2 Custom Error Structure for Detailed Feedback</h3> 
</div>
<div class="line-post">
  Creating a structured error response allows you to include relevant information, such as error codes and timestamps, in every error response. This practice not only improves the API’s clarity but also facilitates debugging. 
</div>
<pre class="code"><span class="keyword-css">public</span> <span class="keyword-css">class</span> ErrorResponse {<br>    <span class="keyword-css">private</span> String errorCode;<br>    <span class="keyword-css">private</span> String message;<br>    <span class="keyword-css">private</span> LocalDateTime timestamp;<br><br>    <span class="keyword-css">public</span> ErrorResponse(String errorCode, String message) {<br>        <span class="keyword-css">this</span>.errorCode = errorCode;<br>        <span class="keyword-css">this</span>.message = message;<br>        <span class="keyword-css">this</span>.timestamp = LocalDateTime.now();<br>    }<br><br>    // Getters and Setters<br>}</pre>
<div class="line-post">
  This structure provides consistency and additional details that help developers trace and understand errors more easily. 
</div>
<div class="line-post"> 
 <h2 class="title-post">2. Advanced Resilience Patterns with Resilience4j</h2> 
</div>
<div class="line-post">
  Beyond simple error handling, robust microservices must recover from transient failures and prevent overloading other services. Resilience4j offers tools to implement retry, circuit breaker, and timeout patterns in Spring Boot, ensuring that services recover gracefully from temporary issues. 
</div>
<div class="line-post"> 
 <h3 class="sub1">2.1 Implementing the Retry Pattern</h3> 
</div>
<div class="line-post">
  The retry pattern automatically retries failed requests, useful for handling intermittent network issues. However, retries should be used judiciously to avoid overwhelming services. 
</div>
<pre class="code"><span class="keyword-css">import</span> io.github.resilience4j.retry.annotation.Retry;<br><br>@RestController<br><span class="keyword-css">public</span> <span class="keyword-css">class</span> PaymentController {<br><br>    @Retry(name = "paymentService", fallbackMethod = "paymentFallback")<br>    @GetMapping("/processPayment")<br>    <span class="keyword-css">public</span> ResponseEntity&lt;String&gt; processPayment() {<br>        // Call to a potentially failing external service<br>        <span class="keyword-css">return</span> <span class="keyword-css">new</span> ResponseEntity&lt;&gt;(paymentService.process(), HttpStatus.OK);<br>    }<br><br>    <span class="keyword-css">public</span> ResponseEntity&lt;String&gt; paymentFallback(Exception e) {<br>        <span class="keyword-css">return</span> <span class="keyword-css">new</span> ResponseEntity&lt;&gt;("Payment Service is currently unavailable. Please <span class="keyword-css">try</span> again later.", HttpStatus.SERVICE_UNAVAILABLE);<br>    }<br>}</pre>
<div class="line-post"> <strong>Explanation:</strong> 
</div>
<div class="line-post"> 
 <ul> 
  <li><strong>Retry Annotation</strong>: The @Retry annotation enables retry logic on the processPayment method, making multiple attempts before invoking the fallback.</li> 
  <li><strong>Fallback Method</strong>: If the service fails after all retry attempts, paymentFallback provides a meaningful response to the client.</li> 
 </ul> 
</div>
<div class="line-post"></div>
<div class="line-post"> 
 <h3 class="sub1">2.2 Using Circuit Breakers to Protect Services</h3> 
</div>
<div class="images-post"> 
 <img width="80%" class="element_975469-image" src="https://resource.tuanh.net/article/image/0f39153e-f1a8-4ded-b0bf-b63603f23224.png" alt="Image"> 
</div>
<div class="line-post">
  Circuit breakers prevent further attempts to call a failing service after a certain threshold is reached, allowing it time to recover. This reduces unnecessary load and avoids cascading failures across dependent services. 
</div>
<pre class="code"><span class="keyword-css">import</span> io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;<br><br>@RestController<br><span class="keyword-css">public</span> <span class="keyword-css">class</span> InventoryController {<br><br>    @CircuitBreaker(name = "inventoryService", fallbackMethod = "inventoryFallback")<br>    @GetMapping("/checkInventory")<br>    <span class="keyword-css">public</span> ResponseEntity&lt;String&gt; checkInventory() {<br>        <span class="keyword-css">return</span> <span class="keyword-css">new</span> ResponseEntity&lt;&gt;(inventoryService.checkAvailability(), HttpStatus.OK);<br>    }<br><br>    <span class="keyword-css">public</span> ResponseEntity&lt;String&gt; inventoryFallback(Exception e) {<br>        <span class="keyword-css">return</span> <span class="keyword-css">new</span> ResponseEntity&lt;&gt;("Inventory Service is temporarily unavailable.", HttpStatus.SERVICE_UNAVAILABLE);<br>    }<br>}</pre>
<div class="line-post"> <strong>Explanation:</strong> 
</div>
<div class="line-post"> 
 <ul> 
  <li><strong>Circuit Breaker Annotation</strong>: This prevents repeated attempts when inventoryService experiences issues, protecting other services from being affected.</li> 
  <li><strong>Fallback Method</strong>: Returns a message indicating that the service is unavailable, giving a controlled response instead of cascading failures.</li> 
 </ul> 
</div>
<div class="line-post"> 
 <h3 class="sub1">2.3 Adding Timeouts to Avoid Long Delays</h3> 
</div>
<div class="line-post">
  Time-outs prevent requests from hanging indefinitely, especially when dealing with services that may not respond promptly. By setting an upper limit, you can ensure that requests don’t block other operations. 
</div>
<pre class="code"><span class="keyword-css">import</span> io.github.resilience4j.timelimiter.annotation.TimeLimiter;<br><span class="keyword-css">import</span> java.util.concurrent.CompletableFuture;<br><br>@RestController<br><span class="keyword-css">public</span> <span class="keyword-css">class</span> OrderController {<br><br>    @TimeLimiter(name = "orderService", fallbackMethod = "orderFallback")<br>    @GetMapping("/placeOrder")<br>    <span class="keyword-css">public</span> CompletableFuture&lt;String&gt; placeOrder() {<br>        <span class="keyword-css">return</span> CompletableFuture.supplyAsync(() -&gt; orderService.processOrder());<br>    }<br><br>    <span class="keyword-css">public</span> CompletableFuture&lt;String&gt; orderFallback(Exception e) {<br>        <span class="keyword-css">return</span> CompletableFuture.completedFuture("Order Service took too <span class="keyword-css">long</span> to respond. Please <span class="keyword-css">try</span> again.");<br>    }<br>}</pre>
<div class="line-post"> <strong>Explanation:</strong> 
</div>
<div class="line-post"> 
 <ul> 
  <li><strong>@TimeLimiter Annotation</strong>: Sets a timeout for placeOrder to avoid hanging if orderService takes too long.</li> 
  <li><strong>Fallback Method</strong>: Returns a message if the request exceeds the timeout, maintaining a seamless experience without indefinite waits.</li> 
 </ul> 
</div>
<div class="line-post"> 
 <h2 class="title-post">3. Logging and Monitoring for Proactive Error Detection</h2> 
</div>
<div class="line-post">
  Effective error handling is incomplete without logging and monitoring. Structured logging provides a consistent format for error messages, while monitoring tools like Zipkin and Prometheus offer insights into service health. 
</div>
<div class="line-post">
  Use structured logging to consistently format logs, making it easier to trace errors across services. 
</div>
<pre class="code"><span class="keyword-css">import</span> org.slf4j.Logger;<br><span class="keyword-css">import</span> org.slf4j.LoggerFactory;<br><br>@Service<br><span class="keyword-css">public</span> <span class="keyword-css">class</span> ProductService {<br>    <span class="keyword-css">private</span> <span class="keyword-css">static</span> <span class="keyword-css">final</span> Logger logger = LoggerFactory.getLogger(ProductService.<span class="keyword-css">class</span>);<br><br>    <span class="keyword-css">public</span> Product getProductById(String id) {<br>        <span class="keyword-css">try</span> {<br>            // Fetch product logic<br>        } <span class="keyword-css">catch</span> (ProductNotFoundException ex) {<br>            logger.error("Product not found with id: {}", id, ex);<br>            <span class="keyword-css">throw</span> ex;<br>        }<br>    }<br>}</pre>
<div class="line-post"> <strong>Explanation:</strong> 
</div>
<div class="line-post"> 
 <ul> 
  <li><strong>Structured Error Information</strong>: Logs include specific details like product id and exception details, improving visibility.</li> 
  <li><strong>Error Consistency</strong>: By logging exceptions consistently, developers can trace issues across distributed services effectively.</li> 
 </ul> 
</div>
<div class="line-post"> 
 <h2 class="title-post">4. Conclusion</h2> 
</div>
<div class="line-post">
  Advanced error handling in Spring Boot microservices requires more than just catching exceptions; it demands a layered approach with custom error responses, resilience patterns, and monitoring. By employing structured logging, global exception handling with @ControllerAdvice, and patterns like retries, circuit breakers, and timeouts, you can build a microservices system that remains resilient in the face of failures. These techniques not only improve user experience but also empower development teams with better insights into system health. 
</div>
<div class="line-post">
  If you have any questions or would like to discuss specific implementation details, feel free to comment below. 
</div><p><strong>Read more at </strong>: <a href='https://www.tuanh.net/blog/spring/techniques-for-robust-error-handling-in-spring-boot-microservices'>Techniques for Robust Error Handling in Spring Boot Microservices</a></p>
