CodeGym /Courses /Module 5. Spring /@ExceptionHandler annotation for exception handling

@ExceptionHandler annotation for exception handling

Module 5. Spring
Level 8 , Lesson 4
Available

Errors are inevitable everywhere, and in programming they're even more so. But instead of letting exceptions "spill out" to the user as confusing error messages or, worse, stack traces, we can catch them and handle them nicely. You've already done this in plain Java. And Spring gives us a tool for that called @ExceptionHandler.

@ExceptionHandler lets us handle exceptions at the level of a specific controller. This greatly simplifies error handling and helps the developer provide consistent app behavior when exceptions occur. For example, instead of showing the user a scary NullPointerException message, we can return a neat JSON response describing the problem.

Basics of working with @ExceptionHandler

The @ExceptionHandler annotation is applied to a method in a controller and indicates which exceptions that method should handle. Here's what it looks like:


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

    @GetMapping("/example")
    public String exampleEndpoint() {
        // Throw an exception for demo
        throw new IllegalArgumentException("Invalid parameter!");
    }

    // Handling IllegalArgumentException
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                             .body("Error: " + ex.getMessage());
    }
}

Here we defined a method annotated with @ExceptionHandler that intercepts IllegalArgumentException and returns a readable error message to the user with HTTP status 400 (BAD_REQUEST).

Key points:

  1. A method with @ExceptionHandler can accept the exception object as an argument to get extra info.
  2. You can return any suitable data type (ResponseEntity, String, Map<String, Object>, etc.).
  3. Nothing prevents the method from handling multiple exception types at once (e.g., by using generics or superclasses).

Example of using @ExceptionHandler

Now let's walk through a real example. Imagine we have a book management app. It should return specific information about a book by its ID.

Controller:


@RestController
@RequestMapping("/books")
public class BookController {

    private final Map<Long, String> books = Map.of(
            1L, "Java for Beginners",
            2L, "Advanced Java",
            3L, "Spring in Action");

    @GetMapping("/{id}")
    public String getBook(@PathVariable Long id) {
        if (!books.containsKey(id)) {
            throw new BookNotFoundException("Book with ID " + id + " not found.");
        }
        return books.get(id);
    }

    @ExceptionHandler(BookNotFoundException.class)
    public ResponseEntity<String> handleBookNotFoundException(BookNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                             .body(ex.getMessage());
    }
}

Custom exception class:


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

Now, if a user sends a GET request to /books/10, they'll get a tidy response:


HTTP Status: 404 Not Found
Body: "Book with ID 10 not found."

Benefits of using @ExceptionHandler

  1. Local handling: the method handles exceptions only for that controller. This is convenient if different controllers have their own specific handling scenarios.
  2. Code cleanliness: moving exception handling to separate methods helps keep the main controller code compact.
  3. Extensibility: it's easy to add handling for new exceptions as needed.

Handling multiple exceptions with @ExceptionHandler

Sometimes one method can handle multiple exception types. This is possible because the @ExceptionHandler annotation accepts an array of exception classes.

Example:


@ExceptionHandler({IllegalArgumentException.class, NullPointerException.class})
public ResponseEntity<String> handleMultipleExceptions(Exception ex) {
    return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                         .body("An error occurred: " + ex.getMessage());
}

Here a single method catches both IllegalArgumentException and NullPointerException, returning the same response for both situations.


Common mistakes when using @ExceptionHandler

  1. Unhandled exceptions: if an exception isn't handled in the current controller, it bubbles up, where it may be caught by a global handler or remain unhandled.
  2. Overriding Spring's default behavior: some exceptions, like MethodArgumentNotValidException, are already handled by Spring by default. If you write your own handler for such exceptions, make sure it covers all possible cases.
  3. Incorrect HTTP status: make sure the returned status matches REST API semantics. For example, NOT_FOUND for missing resources and BAD_REQUEST for validation errors.

Practical task

  1. Create a controller for managing users with a method that returns user info by ID.
  2. Implement a custom exception UserNotFoundException.
  3. Add an @ExceptionHandler for that exception that returns HTTP status 404 and an error message.
  4. Make sure the handler works correctly by sending a request with a non-existent user ID.

Here's what the expected result should look like:

Request:


GET /users/99

Response:


HTTP Status: 404 Not Found
Body: "User with ID 99 not found."

Final notes

@ExceptionHandler is a powerful tool for handling exceptions at the controller level. It lets you write clean, concise code, making debugging easier and improving UX. Use it wisely so your users never see the dreaded "Internal Server Error" and run off to your competitors! ;)

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