Handling Responses

About

When consuming RESTful APIs using RestTemplate, handling responses effectively is crucial to building reliable, observable, and maintainable applications. This includes reading the response body, capturing headers, dealing with different status codes, and handling typed or dynamic responses.

ResponseEntity: The Preferred Wrapper

ResponseEntity<T> is the powerful and flexible way to capture the full HTTP response when making REST calls using RestTemplate.

In a production-grade system, relying solely on response body (getForObject) is often insufficient. We typically need access to status codes, response headers, or even empty bodies (204 No Content). ResponseEntity wraps all of this in one convenient structure.

ResponseEntity represents the entire HTTP response:

ResponseEntity<T> {
   HttpStatus statusCode;
   HttpHeaders headers;
   T body;
}

Unlike getForObject() which returns only the body, ResponseEntity is useful in real-world applications where the complete response context matters — not just the payload.

Example

ResponseEntity<UserDto> response = restTemplate.getForEntity(
    "https://api.example.com/users/{id}",
    UserDto.class,
    userId
);

We can now extract

Handling 204 No Content

Extracting Pagination or Tracking Headers

Building Defensive Services

Mapping Response to Domain Object

When building enterprise-grade applications, it's critical to cleanly separate transport-layer data (e.g., JSON over HTTP) from internal domain models. While RestTemplate allows direct mapping of HTTP response bodies to Java objects, blindly mapping external data structures into internal models can lead to tight coupling, maintenance issues, and data leakage between layers.

This is why response mapping should be treated as a formal step in our architecture — ensuring robustness, decoupling, and clarity.

Instead of directly consuming the HTTP response into our domain model, map the response to a dedicated DTO (Data Transfer Object) and then transform it into a domain entity or business object.

This two-step process:

  1. Maps raw JSON to DTOs using RestTemplate.

  2. Converts DTO to Domain Object using mappers (manual or libraries like MapStruct).

Reason

Details

Decoupling from External API Schema

If external APIs change, we only need to update the DTO, not the domain logic.

Avoid Overexposure

External fields we don’t care about stay out of our core logic.

Validation & Transformation

Apply domain rules or enrich data before creating core objects.

Reusability

Same DTO can be used by other layers or adapters (caching, logging, etc.).

Clean Layering

Keeps domain layer free of protocol-specific artifacts.

Example

1. Response JSON (from external API)

2. DTO for External Response

3. Domain Model

4. Mapping Logic

5. Usage with RestTemplate

Handling JSON Arrays / Lists

When an API responds with a JSON array, it represents a collection of similar entities — like a list of users, orders, or products. In enterprise applications, it’s common to consume such arrays and map them to a list of Java objects for further processing.

Unlike single-object deserialization, handling arrays requires additional care with RestTemplate, especially with Java generics and type erasure.

Example: External API Response

Approach 1: Using ResponseEntity<T[]>

The simplest way to handle a JSON array is to map it into an array of objects:

  • Pros: Simple and readable.

  • Cons: Returns a fixed-size List (from Arrays.asList) unless we wrap it with new ArrayList<>(...).

Approach 2: Using ParameterizedTypeReference<List<T>>

This is the preferred approach in most production systems where generic typing or further abstraction is required.

  • Pros:

    • Supports generics cleanly.

    • Produces a mutable list.

  • Cons: Slightly more verbose.

Dynamic Responses (Map or Raw JSON)

In many real-world APIs, the structure of a response might not be strictly defined or may vary across use cases for instance:

  • Optional fields

  • Nested dynamic objects

  • Polymorphic types

  • Unknown keys or additional metadata

In such cases, mapping directly to a fixed DTO may not work. We need a more flexible way to process semi-structured or dynamic JSON, often using Map, JsonNode, or other raw representations.

Use Case

Why Dynamic

Webhook payloads

Fields can vary between events

External third-party APIs

Schema may not be fixed or may include nested data

Feature-flag responses or config APIs

Keys are often dynamic or environment-based

Error or metadata responses

Varying shape of error payloads

Analytics or audit streams

Fields evolve over time

Approach 1: Using Map<String, Object>

This is the most direct approach to capturing the entire JSON response as a Map.

  • Pros: Straightforward; handles flat or nested dynamic keys.

  • Cons: Type-safety is lost; casting required for deeper levels.

Nested Access Example

Approach 2: Using JsonNode (Jackson Tree Model)

Jackson's JsonNode allows for powerful navigation and inspection of arbitrary JSON.

  • Pros:

    • Non-breaking even when keys are missing.

    • Cleaner navigation and transformation.

  • Cons: Requires familiarity with JsonNode API.

Status Code Checks

When integrating with external systems or microservices, handling HTTP status codes correctly is a critical part of response handling. RestTemplate provides multiple ways to inspect and react to HTTP status codes — whether to retry, log, fail-fast, or switch business logic paths.

Approach 1: Using ResponseEntity

We can access the status code from the ResponseEntity object.

Example with switch-style logic

Approach 2: Custom ResponseErrorHandler

For centralizing status code handling logic, we can create and register a custom error handler.

Register it with RestTemplate

Extracting & Logging Response Headers

HTTP headers play a vital role in metadata exchange between services — including correlation IDs, content types, rate-limiting info, cache controls, and authentication details. When using RestTemplate, it is common in production systems to extract headers for logging, diagnostics, tracing, or making conditional decisions.

Accessing Headers with ResponseEntity

The most straightforward way is via ResponseEntity.getHeaders():

Iterating Over All Headers

In enterprise applications, it’s often useful to log or analyze all response headers.

Example: Handling Rate Limit Headers

Some APIs send rate-limiting metadata in headers:

Accessing Headers in POST, PUT, DELETE Requests

Even when using methods like postForEntity or exchange, we can still access headers:

Structured Header Logging

Enterprise services often use structured logs for better filtering and correlation. Here's how we would build a structured logging message:

Handling Empty Responses

In real-world service integrations, it is common for REST APIs to return responses with no body — either because:

  • The operation was successful but doesn’t return data (e.g., DELETE, PUT, 204 No Content)

  • The response is dynamically constructed and might sometimes be empty (e.g., optional search results)

  • The downstream service failed silently or intentionally suppressed the payload

If not handled carefully, these empty responses can lead to NullPointerException, HttpMessageNotReadableException, or deserialization failures, especially when using generic or POJO-based deserialization.

Typical HTTP Scenarios

HTTP Status

Description

Behavior

200 OK

May contain an empty body

Safe to check for null or empty

204 No Content

No content expected

Response body will be null

404 Not Found

Often no body returned

Should handle gracefully if optional

500/503

Error response, possibly empty

Fallback or error handling needed

Handling with ResponseEntity

Handling with String.class to Safely Inspect Body

This approach is useful when:

  • The response format is dynamic or loosely typed

  • We want to inspect or log the raw body before parsing

Using ParameterizedTypeReference with Exchange

When using generics (e.g., List<MyType>), ensure safe parsing by checking body before access:

Avoiding HttpMessageNotReadableException

Sometimes, RestTemplate may try to parse an empty body into a class and fail.

To prevent this:

Option 1: Use String.class and parse only if not empty

Option 2: Catch parsing exceptions

Centralized Response Handling Using Utility

In large-scale Spring applications, interactions with external APIs are widespread across various services. Managing response deserialization, error handling, status code verification, and empty responses individually can lead to duplicated logic and inconsistent behavior.

A centralized response handling utility abstracts this logic in a reusable, maintainable, and testable manner.

Basic Utility Design

Step 1: Generic Response Mapper

Step 2: Generic Utility with Status Code Checks

Step 3: Example Usage in Service Layer

Variation for Optional Use Cases

Example Use: Optional Behavior

Deserializing Nested Responses

In real-world APIs, responses are often wrapped or nested, meaning the actual business data is embedded within a larger structure containing metadata, status codes, pagination info, or wrapper fields.

Instead of directly mapping to domain objects, we must first extract the inner content — and this requires custom deserialization logic or wrapper types.

Common Real-World API Pattern

Many APIs return data in the following shape:

Here, our domain object is just the "data" part.

Approach 1: Wrapper POJO Mapping

Step 1: Define a Generic Wrapper

Step 2: Define Domain Object

Step 3: Parameterized Type Reference

Approach 2: Deserialize Using ObjectMapper and Extract Inner Field

When type inference is complex, use JsonNode or manual extraction:

Handling Lists in Nested Structure

With Generic Wrapper

Advanced Use Case: Pagination with Metadata

POJO Structure

Then:

Last updated