Validation and errors
Hub › Java › Intermediate › Validation and errors
Goal
Add an exception handler that maps ItemNotFoundException to HTTP 404 and MethodArgumentNotValidException to HTTP 400, both with a consistent JSON body. After this page your API returns clean error responses instead of Spring's default HTML error page.
Prerequisites
Why a dedicated exception handler
When @Valid rejects a request body, Spring throws MethodArgumentNotValidException. When ItemService.get does not find a record, it throws ItemNotFoundException. Without a handler, both exceptions bubble up to Spring's default /error endpoint, which returns a verbose JSON blob (or HTML, depending on the Accept header).
A @RestControllerAdvice class intercepts exceptions before they reach the default handler, letting you return exactly the error shape you want.
ApiExceptionHandler
Create src/main/java/com/example/itemsapi/ApiExceptionHandler.java:
package com.example.itemsapi;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Map;
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(ItemNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Map<String, String> notFound(ItemNotFoundException e) {
return Map.of("error", e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> invalid(MethodArgumentNotValidException e) {
return Map.of("error", "name is required");
}
}Key points:
@RestControllerAdvice— combines@ControllerAdvice(applies globally to all controllers) with@ResponseBody(return value is serialized to JSON). Spring picks it up automatically because it is a component in the same package.@ExceptionHandler(ItemNotFoundException.class)— declares that this method handlesItemNotFoundException. Spring calls it whenever that exception propagates out of a controller method.@ResponseStatus(HttpStatus.NOT_FOUND)— sets the HTTP status to 404.Map.of("error", e.getMessage())— returns{"error":"item 42 not found"}.Map.ofis immutable and Jackson serializes it to JSON automatically.- The
invalidhandler keeps the error message generic. You could inspecte.getBindingResult()to build field-level messages, but a single message is enough for this tier.
Verify: error responses
Start the app and test both error paths:
./mvnw spring-boot:runIn a second terminal:
# POST with blank name — expect 400
curl -i -X POST http://localhost:8080/items \
-H 'Content-Type: application/json' \
-d '{"name":""}'Expected response:
HTTP/1.1 400
{"error":"name is required"}# GET non-existent id — expect 404
curl -i http://localhost:8080/items/999Expected response:
HTTP/1.1 404
{"error":"item 999 not found"}Stop the server with Ctrl-C.
Next → Tests