Java and Spring Boot: Designing Consistent REST API Errors

A REST API needs more than successful responses. Clients must also understand validation failures, missing resources, authorization problems, and unexpected server errors. When every endpoint returns a different error structure, frontend development and API integration become unnecessarily complicated.

Define a consistent error contract

An error response should communicate the HTTP status, a human-readable description, and enough context for the client to respond appropriately.

Spring applications can use a consistent JSON structure:

{
  "status": 404,
  "title": "Resource not found",
  "detail": "The requested customer does not exist."
}

This is an illustrative structure. Spring’s ProblemDetail support can provide a standardized representation of HTTP errors without requiring a custom format for every application.

Handle expected exceptions centrally

Spring’s @RestControllerAdvice allows exception handling to be shared across controllers.

@RestControllerAdvice
public class ApiExceptionHandler {

    @ExceptionHandler(CustomerNotFoundException.class)
    public ResponseEntity<ProblemDetail> handleNotFound(
            CustomerNotFoundException exception) {

        ProblemDetail problem =
                ProblemDetail.forStatus(HttpStatus.NOT_FOUND);

        problem.setTitle("Customer not found");
        problem.setDetail(exception.getMessage());

        return ResponseEntity
                .status(HttpStatus.NOT_FOUND)
                .body(problem);
    }
}

The example assumes that CustomerNotFoundException is an application-defined exception. In production, ensure exception messages do not expose internal implementation details or sensitive data.

Distinguish error types

Use status codes consistently:

  • 400 Bad Request for malformed or invalid requests.
  • 401 Unauthorized when authentication is required or invalid.
  • 403 Forbidden when the authenticated caller lacks permission.
  • 404 Not Found when the requested resource does not exist.
  • 409 Conflict when the request conflicts with the current resource state.
  • 500 Internal Server Error for unexpected server failures.

The exact choice depends on the API contract and security requirements. Avoid returning 200 OK with an error hidden inside the response body.

Validate incoming requests

Bean Validation can handle common request constraints:

public record CreateCustomerRequest(
        @NotBlank String name,
        @Email @NotBlank String email) {
}

A controller can activate validation with @Valid:

@PostMapping("/customers")
public ResponseEntity<Void> createCustomer(
        @Valid @RequestBody CreateCustomerRequest request) {

    customerService.create(request);

    return ResponseEntity.status(HttpStatus.CREATED).build();
}

The application should define how validation errors are represented so that clients receive predictable field-level feedback.

Log unexpected failures safely

Clients need useful information, but they should not receive stack traces, SQL statements, credentials, or internal service details.

Log unexpected exceptions with a correlation identifier where possible. This lets operators investigate failures without exposing sensitive information in public responses.

Test the error contract

Tests should verify both status codes and response bodies for validation failures, missing resources, authorization failures, and unexpected exceptions.

These tests protect the API contract when controllers, exception handlers, or framework versions change.

Conclusion

Consistent REST API errors reduce integration complexity and improve diagnostics. Centralized exception handling, validation, appropriate status codes, and safe logging help make Spring Boot APIs predictable for both clients and operators.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *