CodeGym /Courses /Module 5. Spring /Error Handling in Spring MVC

Error Handling in Spring MVC

Module 5. Spring
Level 8 , Lesson 3
Available

Errors in an application are, unfortunately, inevitable. Bad user input, database query failures, or an external service going down — these are all common situations that can lead to exceptions. As a developer you need to handle these correctly so they don't crash in front of the end user as scary stack traces.

Spring MVC provides built-in mechanisms for that:

  • Local error handling: using methods inside controllers.
  • Global error handling: using the @ControllerAdvice annotation.

Error handlers help you to:

  • Hide internal details (for example, stack traces).
  • Show the user a clear and useful error message.
  • Return an appropriate HTTP status code (for example, 400 Bad Request or 500 Internal Server Error).
  • Perform centralized logging of errors.

Local error handling

Local error handling means handling exceptions within a single controller. In Spring this is done with the @ExceptionHandler annotation. It lets you map an exception to a method that should be called when that exception occurs.

Example of a local handler Let's look at an example where we handle MethodArgumentNotValidException, which gets thrown during validation of incoming data.


@RestController
@RequestMapping("/api/example")
public class ExampleController {

    @PostMapping("/create")
    public String createExample(@Valid @RequestBody ExampleDto dto) {
        // Your request handling code
        return "Example created successfully!";
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException exception) {
        // Extract the first validation error to return to the user
        String errorMessage = exception.getBindingResult()
                                       .getFieldErrors()
                                       .get(0)
                                       .getDefaultMessage();
        return ResponseEntity.badRequest().body("Validation Error: " + errorMessage);
    }
}

In this code:

  • If validation of the ExampleDto finds a violation (for example, the name field is empty), a MethodArgumentNotValidException will be thrown.
  • The @ExceptionHandler will catch it and return a clear error message to the user.

Local handling is fine for small apps, but it becomes insufficient when you have many controllers that should react the same way to the same exceptions. In those cases it's convenient to use global error handling.


Global error handling

Global error handling with @ControllerAdvice lets you define a single set of exception handlers that apply to all controllers in the app.

@ControllerAdvice is a special Spring annotation that allows you to create a centralized exception handler. All methods annotated with @ExceptionHandler inside a class annotated with @ControllerAdvice will be called to handle exceptions thrown in any controller of the application.

Example of a global handler Create a class to handle errors:


@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException exception) {
        // Collect messages about all validation errors
        String errorMessage = exception.getBindingResult()
                                       .getFieldErrors()
                                       .stream()
                                       .map(error -> error.getField() + ": " + error.getDefaultMessage())
                                       .collect(Collectors.joining(", "));
        return ResponseEntity.badRequest().body("Validation Errors: " + errorMessage);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGeneralException(Exception exception) {
        // Log the error (in a real project you can use a logger)
        System.err.println("Unexpected error: " + exception.getMessage());
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                             .body("An unexpected error occurred. Please try again later.");
    }
}

In this code:

  • The handleValidationException method handles only validation errors (MethodArgumentNotValidException) and returns a friendly message that aggregates all errors.
  • The handleGeneralException method catches any other exceptions and returns a message about an internal server error.

How does it work?

When an exception occurs, Spring first checks if there's a local handler in the current controller. If none is found, it looks for a suitable method in the class annotated with @ControllerAdvice.


Benefits of global error handling

Using @ControllerAdvice has several advantages:

  • Centralized management of exception handling.
  • Code reuse (no need to duplicate error handling logic in every controller).
  • Ability to use polymorphism (you can create an exception hierarchy and handle it "in a chain").

Example: handling custom exceptions

In real projects you often create custom exceptions for specific situations. For example, a ResourceNotFoundException for when an entity isn't found.

Create the exception class:


public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

Handling the exception Add a handler to GlobalExceptionHandler:


@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<String> handleResourceNotFound(ResourceNotFoundException exception) {
    return ResponseEntity.status(HttpStatus.NOT_FOUND).body(exception.getMessage());
}

Using it in a controller

You can now throw ResourceNotFoundException when a resource isn't found:


@GetMapping("/item/{id}")
public Item getItem(@PathVariable Long id) {
    return itemRepository.findById(id)
                         .orElseThrow(() -> new ResourceNotFoundException("Item with ID " + id + " not found"));
}

If the item with the given id is missing from the database, the user will receive a 404 Not Found response with the message "Item with ID {id} not found".


Practical tips

  1. When creating custom exceptions avoid overcomplicating the class hierarchy. Keep it clear and concise.
  2. Always log errors, but don't show internal details to users (for example, SQL queries or stack traces).
  3. Set appropriate HTTP status codes in responses (for example, 400 for validation errors, 404 for missing resources, 500 for internal errors).
  4. Include the error cause in the response description to make diagnosing issues easier.

Practical exercise

Task: implement a global error handler that:

  1. Handles validation errors (MethodArgumentNotValidException) and returns an aggregated list of errors.
  2. Handles cases where a resource is not found (ResourceNotFoundException).
  3. Catches all other exceptions and returns the standard message "Internal Server Error".

Try integrating this handler into your current project and test different error scenarios.


At this point your code will become more reliable: you'll be able to show the user useful information instead of a "pretty" stack trace full of errors. Now any exception in your system will be handled elegantly and centrally.

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION