DEV Community

Pratik280
Pratik280

Posted on

Production-Grade Spring Boot APIs — Part 3: Consistent Responses & Global Exception Handling

In real systems, APIs don’t fail gracefully by default.

Without discipline, you end up with:

  • Different response formats per endpoint
  • Unclear error messages
  • Controllers full of try-catch blocks

This article explains how we design predictable, consistent API responses using a common response wrapper and centralized exception handling.

This is Part 3 of the Production-Grade Spring Boot API Design series.

Code Gihub Repo: https://github.com/Pratik280/order-system/tree/develop


What this post covers

  • Why API response consistency matters in microservices
  • Designing a generic BaseResponse
  • Builder pattern in API responses
  • ResponseEntity and real HTTP semantics
  • Global exception handling with @RestControllerAdvice
  • Translating domain exceptions into HTTP responses

Why we need a common response format

In microservices:

  • APIs must look identical
  • Errors must be predictable
  • Clients must not guess formats

BaseResponse — A Single Contract for All APIs

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class BaseResponse<T> {
    private int status;
    private String message;

    // success payload
    private T data;

    // error details
    private List<Map<String, Object>> errors;
}
Enter fullscreen mode Exit fullscreen mode

Why this structure works

  • Generic () — supports any response payload
  • Clear separation:
    • data → success responses
    • errors → failure responses
  • Consistent fields across all endpoints
  • Easy for clients to consume and validate

Success vs Error clarity

  • Success response → data is filled, errors is null
  • Error response → errors is filled, data is null

This eliminates ambiguity for API consumers.

Builder Pattern for Clean Response Creation

Instead of manually constructing responses everywhere, we centralize response creation.

ResponseBuilder.java

public class ResponseBuilder {

    public static <T> ResponseEntity<BaseResponse<T>> success(
            HttpStatus status, String message, T data ){

        BaseResponse<T> baseResponse = new BaseResponse<>(
                status.value(),
                message,
                data,
                null
        );

        return ResponseEntity.status(status).body(baseResponse);
    }

    public static <T> ResponseEntity<BaseResponse<T>> error(
            HttpStatus status, String message, List<Map<String, Object>> errors ){

        return ResponseEntity.status(status)
                .body(BaseResponse.<T>builder()
                        .status(status.value())
                        .message(message)
                        .errors(errors)
                        .build());
    }
}
Enter fullscreen mode Exit fullscreen mode

Why a ResponseBuilder?

  • Prevents duplication across controllers
  • Enforces one response format everywhere
  • Reduces mistakes in status codes and payloads
  • Makes controllers thin and readable

Controllers should orchestrate requests, not construct response objects.


Why Controllers Should NOT Handle Exceptions

A common beginner mistake is this:

try {
   // business logic
} catch (Exception e) {
   // return error response
}
Enter fullscreen mode Exit fullscreen mode

In production, this leads to:

  • Repeated try-catch blocks
  • Inconsistent error responses
  • Bloated controllers
  • Hard-to-maintain code

Global Exception Handling with @RestControllerAdvice

GlobalExceptionHandler.java

@RestControllerAdvice
public class GlobalExceptionHandler {
Enter fullscreen mode Exit fullscreen mode

What @RestControllerAdvice does

  • Applies to all controllers
  • Automatically returns JSON responses
  • Centralizes exception-to-response translation

It is the REST equivalent of a global error boundary.


Handling Domain Exceptions with @ExceptionHandler

@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<BaseResponse<Object>> handleResourceNotfound(
        ResourceNotFoundException exception,
        HttpServletRequest request ){

    return ResponseBuilder.error(
            HttpStatus.NOT_FOUND,
            exception.getMessage(),
            List.of(Map.of(
                    "code", "RESOURCENOTFOUND",
                    "message", exception.getMessage(),
                    "path", request.getRequestURI()
            ))
    );
}
Enter fullscreen mode Exit fullscreen mode

Why this is production-grade

  • Services throw domain-specific exceptions
  • Controllers remain clean
  • HTTP status codes are correct
  • Error payloads are structured and informative

Each error includes:

  • Error code (for frontend / monitoring)
  • Human-readable message
  • Request path (debugging)

Custom Exceptions Represent Business Failures

public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message){
        super(message);
    }
}
Enter fullscreen mode Exit fullscreen mode

Why custom exceptions matter

  • Express business meaning, not technical failure
  • Decouple service logic from HTTP concerns
  • Make error handling explicit and readable

Services should throw what went wrong, not how to respond.


The End Result

With this approach:

  • Every API response looks the same
  • Error handling is centralized
  • Controllers stay lean
  • APIs are predictable and client-friendly
  • The system scales across teams

This is the difference between APIs that merely work and APIs that survive production.


Final Thoughts

This is the exact structure we followed in real Spring Boot microservices:

  • Clean layers
  • Explicit dependencies
  • Consistent APIs
  • Interview‑defensible design

Production‑grade Spring Boot is less about annotations — and more about discipline and structure.


This concludes the Production-Grade Spring Boot API Design series — based on real project experience and refined personal notes.

The goal was simple:
APIs that are clean, consistent, scalable, and interview-ready.


📌 This blog is based on real project experience and refined personal notes.

Top comments (1)

Collapse
 
shashwathsh profile image
Shashwath S H

Great content, helped a lot to understand exception handling. If you're interested in spring boot related content, follow me for such content. I upload daily on fundamental topics of Spring Boot, you might like that..... Happy learning