Overview
In this post, we’re going to talk about what exceptions are and how to handle errors in a Java project using @ControllerAdvice
. We’ll go over creating custom exceptions and sending back the right HTTP status codes. By the end, your API will be easier to maintain, more reliable, and give your users clearer error messages.
What is an exception?
Let’s start with the basics: what’s an exception?
An exception is basically Java’s way of saying, “Hey, something went wrong!” and it stops the normal happy flow of your program.
It usually occurs when something unexpected happens, like:
- Trying to access a file that doesn’t exist.
- Dividing by zero.
- Our always friend, NullPointerException.
When an exception occurs:
- Java creates an exception object with details about the error.
- The regular flow of the application stops.
- Java tries to find a matching handler to deal with the exception.
Types of exceptions
There are two main types of exceptions:
Checked exceptions → Must be either caught or declared (e.g., IOException).
Unchecked exceptions (Runtime exceptions) → Don’t require explicit handling, often indicate programming errors (e.g., NullPointerException).
In REST APIs, unchecked exceptions are the most common. Without proper handling, they return 500 Internal Server Error with a raw stack trace—which is not user-friend which is what we will try to improve later in this article.
Why Should we Handle Exceptions?
- Prevent internal errors from leaking stack traces to clients.
- Respond with meaningful HTTP status codes (404, 400, 500, etc.).
- Provide consistent and clear error messages.
- Centralize error handling.
Create custom exceptions
A good practice in any project is to define your own exception classes for common business errors, for example:
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
Other examples of custom exceptions is BadRequestException
, UnauthorizedException
, etc
Use @ControllerAdvice for Global Exception Handling
Use @ControllerAdvice
to define that this class will be the exception handler for the project.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<String> handleNotFound(ResourceNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneral(Exception ex) {
// this will catch any exception extending from Excepcion.class
return new ResponseEntity<>("Internal server error", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Throw exceptions in your services or controllers
Now that we've defined our custom exceptions, and the global exception handler then we can safely throw exceptions.
@GetMapping("/items/{id}")
public Item getItem(@PathVariable Long id) {
return itemRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Item with ID " + id + " not found"));
}
Client Response Examples
With everything defined we will have nice and well understanding exceptions like the following.
Resource Not Found
When requesting a non-existent resource:
HTTP/1.1 404 Not Found
Body: "Item with ID 10 not found"
For internal errors (e.g., unhandled NullPointerException):
HTTP/1.1 500 Internal Server Error
Body: "Internal server error"
Conclusion
Handling exceptions centrally with @ControllerAdvice
and using custom exceptions helps you build clean, robust APIs. This improves the client experience and makes your code easier to maintain.
Top comments (0)