Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Spring MVC ProblemDetail: stable errors without leaking internals

Last updated: 1 Oct 20264 min read
tutorial
IntermediateBy AITrove Editorial

ProblemDetail represents a structured HTTP error with a status and detail that clients can interpret without parsing an exception stack trace.

Download Spring source kit

This lesson uses the downloadable source kit: Java 21, Spring Boot 4.0.8 and its managed Spring Framework 7 dependencies. The version is pinned for repeatable builds.

Translate at the response boundary

ReceiptErrors converts MissingReceipt into 404 and rejected input into 400. The client sees a specific public message for a missing receipt and a generic validation message for rejected input. Internal SQL, stack traces and credentials do not belong in either response.

The missing-receipt test checks application/problem+json rather than only the status. A response can carry the right status but the wrong media type or body shape, which makes the client contract harder to consume.

Do not turn every exception into success

A broad handler that converts all failures into 200 hides server faults from retry and monitoring logic. Separate invalid input, authorization failures and unexpected infrastructure failures. Log server failures with an internal correlation identifier while keeping sensitive data out of public details.

This fixture does not define a stable machine-readable error-code taxonomy. When clients need to act on distinct domain failures, add and test that taxonomy without requiring them to inspect English sentences.

Checked source

Java
package in.aitrove.learning;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class ReceiptErrors {
    @ExceptionHandler(ReceiptController.MissingReceipt.class)
    ProblemDetail missing(ReceiptController.MissingReceipt failure) {
        return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, failure.getMessage());
    }
    @ExceptionHandler({IllegalArgumentException.class, MethodArgumentNotValidException.class})
    ProblemDetail rejected(Exception failure) {
        return ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Receipt input is invalid");
    }
}

Test the boundary

Run mvn test in the source-kit directory. ReceiptBoundaryTest.missingReceiptUsesProblemDetail and rejectsZeroAmount checks the behavior described here. Java excerpts belong to the named source-kit classes; they are not independent source files unless the complete class is shown.

Costs and boundaries

Constructing an error object is bounded work here; exception logging, serialization and repeated invalid requests can dominate a real failure path. Rate limits and observability are separate controls.

Common Mistakes

  • Never return stack traces or SQL as public detail.
  • Do not convert server errors into successful status codes.
  • Clients should not parse prose to decide business actions.

Read next

Spring MVC request validation: reject invalid commands before mutation, Mockmvc tests, Java exceptions: recovery boundaries and failure context.

Continue with checked Spring boundaries

Continue with Spring MVC validation errors: one public ProblemDetail for two failure paths.

Checked follow-up

Continue with Spring MVC exception handlers: a controller-local handler wins before global advice, Spring @RestControllerAdvice selector: apply an error policy to one controller family.

GraphQL continuation

Continue with Spring GraphQL errors: distinguish validation, resolver failure and nullable data.

spring
spring-boot
problem-details
Storage details