Explain Codes LogoExplain Codes Logo

Spring Boot REST service exception handling

java
exception-handling
spring-boot
rest-service
Alex KataevbyAlex Kataev·Mar 7, 2025
TLDR

To proficiently handle exceptions in your Spring Boot REST service, couple @RestControllerAdvice with @ExceptionHandler. This combination allows for broad and specific exception management. Here's a simple illustration:

@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<String> handleException(Exception e) { String errorUUID = logErrorToNoSql(e); return ResponseEntity .status(HttpStatus.INTERNAL_SERVER_ERROR) // Adding a tasteful hint of mystery to the usual "Error Occurred" message! .body("Our trained monkeys are onto something unexpected! Use this ID for reference: " + errorUUID); } private String logErrorToNoSql(Exception e) { // Not just any log; Remember, we're talking NoSql with a decorated UUID! return UUID.randomUUID().toString(); } }

This "catch-all" strategy captures every Exception type. It ends by presenting a 500 Internal Server Error furnished with a mysterious UUID. Feel free to tweak for more specific exception types as per your needs.

Leveling up API Error Responses with ResponseEntityExceptionHandler

Extending ResponseEntityExceptionHandler casts a normative shape to your API error responses. What's more, it ensures even exceptions dealt natively by Spring Boot get a touch of your style:

@ControllerAdvice public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler { // Be your own superhero; Create more custom handlers as needed! @Override protected ResponseEntity<Object> handleMethodArgumentNotValid( MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, "Not cool; Validation error", ex.getBindingResult().toString()); return new ResponseEntity<>(apiError, headers, status); } // ApiError is 'the chosen one'. Make sure you define it with much thought! }

Not an ordinary 404 - Custom 404 Responses

Spring Boot provides an elegant way to tailor NoHandlerFoundException. Set spring.mvc.throw-exception-if-no-handler-found=true in application.properties and you're good to go. Here's a handler for that in your star-chamber advice class:

@ExceptionHandler(NoHandlerFoundException.class) public ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, WebRequest request) { ApiError apiError = new ApiError(HttpStatus.NOT_FOUND, "Hmmm, that resource is pulling a Houdini...vanished!", ex.getMessage()); return new ResponseEntity<>(apiError, new HttpHeaders(), apiError.getStatus()); }

When life throws Errors, handle them with Aspects!

Consider an aspect-oriented synergy to convert errors into calmer exceptions:

@Aspect @Component public class ErrorHandlingAspect { @Around("execution(* com.yourcompany.*.*(..))") public Object handleErrors(ProceedingJoinPoint joinPoint) throws Throwable { try { return joinPoint.proceed(); } catch (Error error) { // Warning: Do not try this at home! throw new CustomException("Houston, we have an Error (Gasp) ... no worries, it's an Exception now.", error); } } }

Aspects are cool, but remember, they can be overreaching. Scope them carefully or they might end up at awkward places.

Pitfalls to avoid in your Exception handling journey

Stay away from @EnableWebMvc. It disables Spring Boot's auto-configuration like Kryptonite to Superman! Instead, rely on @ComponentScan and @EnableAutoConfiguration to work their Spring Boot magic. Also, disabling the auto-mapping of static resources can be a booster for performance in a pure RESTful service.

Testing 1,2,3... with MockMvc!

One of the best ways to affirm correct exception responses is through unit testing. Use MockMvc for your unit tests!

@RunWith(SpringRunner.class) @WebMvcTest public class ExceptionHandlerControllerTest { @Autowired private MockMvc mockMvc; @Test public void whenMethodArgumentNotValid_thenBadRequest() throws Exception { mockMvc.perform(post("/endpoint") .content("{...}") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.error").value("Not cool; Validation error")); } }

Nothing says "I'm working fine!" like green-lit tests!

Final Thoughts

Experience the quiet charm of a custom error class that provides a sense of uniformity!

Learn to weigh the pros and cons of automatic configuration; disable what's unnecessary!

When exceptions are predictable, use @ResponseStatus to directly set the HTTP status!

Consider using ModelAndView when you want to go all out and showcase a custom error page!

On your learning voyage, remember to examine open-source code for fresh perspectives and ideas!

Conserve your energy by avoiding over-specificity; embrace a more general approach towards exception handling!