Configuration

About

RestTemplate is a central class in Spring that allows applications to make HTTP calls to external services in a simple and declarative way. However, just using new RestTemplate() is rarely enough. In real-world applications, RestTemplate needs proper configuration to handle timeouts, error handling, authentication, message conversion, and performance tuning.

Creating and Registering a RestTemplate Bean

The best practice is to create a single RestTemplate bean and inject it wherever needed.

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder.build();
    }
}

Using RestTemplateBuilder allows us to apply global settings such as timeouts, interceptors, and message converters.

Setting Timeouts

Timeouts are a critical part of robust system design. They help prevent our application from hanging indefinitely when a remote service is slow, overloaded, or unresponsive.

In enterprise applications, we should never rely on the default timeout settings, as they are often unbounded or too generous.

Types of Timeouts We Should Configure

In the context of RestTemplate, we typically configure two timeouts

Timeout Type
Description

Connection Timeout

Time allowed to establish the TCP connection to the target server.

Read Timeout

Time to wait for the response after sending the request. If the server is slow to respond or never responds, this will kick in.

Optional (depending on request factory)

Timeout Type
Description

Connection Request Timeout

Time to wait for a connection from the connection pool (for pooled HTTP clients). Useful when we are reusing HTTP connections.

1. Using SimpleClientHttpRequestFactory

This is the default and simplest HTTP request factory provided by Spring. It directly uses the java.net.HttpURLConnection under the hood.

Characteristics

  • Lightweight and easy to set up.

  • Good for basic use cases with low concurrency.

  • No support for connection pooling.

  • Limited to blocking I/O operations.

  • Best suited for small applications, internal tooling, or test utilities.

SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(3000);  // milliseconds
factory.setReadTimeout(8000);

RestTemplate restTemplate = new RestTemplate(factory);

When to Use

  • When building non-critical or internal apps with low request volume.

  • In situations where we don't require advanced HTTP features like pooling, connection reuse, or retry strategies.

  • For rapid prototyping and quick integrations.

Limitations

  • No connection reuse: a new connection is created for each request.

  • Cannot tune many HTTP-layer concerns (e.g., keep-alive, socket buffering, etc.).

  • Not suitable for production-level microservices or distributed systems.

2. Using HttpComponentsClientHttpRequestFactory

Backed by Apache HttpClient, this factory allows advanced HTTP capabilities including:

  • Connection pooling

  • Retry policies

  • Custom headers, interceptors

  • SSL configuration

  • Request-level tuning

Characteristics

  • Supports fine-grained timeout control: connection timeout, read timeout, and connection request timeout.

  • Integrates well with enterprise-grade HTTP configurations.

  • Suitable for high-performance and scalable applications.

  • Supports connection reuse, improving performance.

HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(3000);
factory.setReadTimeout(10000);
factory.setConnectionRequestTimeout(2000);

RestTemplate restTemplate = new RestTemplate(factory);

When to Use

  • For enterprise-scale applications or microservices communicating over HTTP.

  • When we need connection pooling for performance optimization.

  • If we are interacting with external APIs or services with potential for high latency.

Additional Benefits

  • Can plug in a custom HttpClient with additional configurations (e.g., proxy, TLS versions, keep-alive).

  • Better control for timeouts per route or host.

3. Using RestTemplateBuilder

A Spring Boot convenience builder for creating RestTemplate instances in a more declarative and chainable manner.

Characteristics

  • Encourages cleaner, more readable code.

  • Automatically injects common configurations (e.g., message converters, interceptors).

  • Easily integrates with application properties, profiles, and dependency injection.

  • Under the hood, it can use any ClientHttpRequestFactory, most commonly the Apache one in Spring Boot setups.

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder
        .setConnectTimeout(Duration.ofSeconds(3))
        .setReadTimeout(Duration.ofSeconds(10))
        .build();
}

When to Use

  • In Spring Boot applications, where idiomatic configuration and centralization are preferred.

  • When we want to keep timeout and other settings externalized via config files (YAML/properties).

  • If we want to create preconfigured RestTemplate beans shared across services with minimal boilerplate.

We can combine it with .requestFactory(...) to fully customize the underlying factory (e.g., inject Apache HttpClient).

Advantages

  • Aligns well with Spring Boot auto-configuration.

  • Works seamlessly with @ConfigurationProperties for dynamic timeout settings.

  • Useful for unit testing, as the builder can be mocked or overridden easily.

Adding Interceptors

Interceptors in RestTemplate allow we to intercept HTTP requests and responses before they are sent and after they are received. This provides a powerful mechanism to:

  • Enrich or modify the request (e.g., add headers)

  • Log outgoing and incoming traffic

  • Propagate context (like trace IDs, auth tokens)

  • Handle cross-cutting concerns like metrics, retries, or API versioning

They are analogous to servlet filters but applied to outbound HTTP calls.

Interface: ClientHttpRequestInterceptor

Each interceptor implements the following interface:

public interface ClientHttpRequestInterceptor {
    ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
        throws IOException;
}
  • request: Contains metadata like headers and URL.

  • body: Raw request body in bytes.

  • execution: Used to proceed with the actual call.

How to Register Interceptors ?

We can add interceptors to a RestTemplate either programmatically or through RestTemplateBuilder.

Registering Manually

@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
    interceptors.add(new HeaderAddingInterceptor());
    interceptors.add(new LoggingInterceptor());
    restTemplate.setInterceptors(interceptors);
    return restTemplate;
}

Example

1. Add Standard Headers (e.g., Authentication, Correlation IDs)

public class HeaderAddingInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body,
                                        ClientHttpRequestExecution execution) throws IOException {
        request.getHeaders().add("X-Correlation-ID", MDC.get("correlationId"));
        request.getHeaders().add("Authorization", "Bearer " + getJwtToken());
        return execution.execute(request, body);
    }

    private String getJwtToken() {
        // Retrieve from thread-local storage or context
        return SecurityContextHolder.getContext().getAuthentication().getCredentials().toString();
    }
}

This is useful when every service call needs security and traceability.

2. Centralized Logging of Requests/Responses

public class LoggingInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body,
                                        ClientHttpRequestExecution execution) throws IOException {

        logRequest(request, body);

        ClientHttpResponse response = execution.execute(request, body);

        logResponse(response);

        return response;
    }

    private void logRequest(HttpRequest request, byte[] body) {
        System.out.println("Sending Request to URI: " + request.getURI());
        System.out.println("Headers: " + request.getHeaders());
        System.out.println("Body: " + new String(body, StandardCharsets.UTF_8));
    }

    private void logResponse(ClientHttpResponse response) throws IOException {
        System.out.println("Response Status: " + response.getStatusCode());
        // Avoid reading body here if the stream will be consumed downstream
    }
}

This is critical in environments where auditing, debugging, or API telemetry is required.

3. Dynamic API Key Injection Based on Service

public class ApiKeyRoutingInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body,
                                        ClientHttpRequestExecution execution) throws IOException {

        URI uri = request.getURI();
        if (uri.getHost().contains("payment-service")) {
            request.getHeaders().add("x-api-key", "payment-service-key");
        } else if (uri.getHost().contains("inventory-service")) {
            request.getHeaders().add("x-api-key", "inventory-service-key");
        }

        return execution.execute(request, body);
    }
}

Used in multi-tenant or multi-provider integrations.

Custom Message Converters

In Spring’s RestTemplate, message converters are responsible for serializing Java objects into HTTP request bodies and deserializing HTTP response bodies into Java objects. These converters implement the interface HttpMessageConverter.

Spring provides a set of default converters (like MappingJackson2HttpMessageConverter for JSON), but we can register our own converters when:

  • Working with custom media types

  • Using an alternative serialization library (e.g., Gson, Protobuf)

  • Customizing how objects are serialized/deserialized (e.g., date formats, field naming, null handling)

Default Converters

Spring Boot pre-configures the following (based on dependencies):

Converter Class
Purpose
Media Type

StringHttpMessageConverter

Text/plain

text/plain

MappingJackson2HttpMessageConverter

JSON using Jackson

application/json

Jaxb2RootElementHttpMessageConverter

XML via JAXB

application/xml

FormHttpMessageConverter

Form data

application/x-www-form-urlencoded

These are added to RestTemplate when it’s auto-configured via RestTemplateBuilder.

Why Customize Message Converters ?

Scenario
Example

Need to support additional serialization formats

Protocol Buffers, Avro, Smile

Require fine-tuned Jackson behavior

Use snake_case, omit nulls, custom serializers

Work with encrypted or compressed payloads

Custom stream-based converter

Our API uses non-standard media types

e.g., application/vnd.company.v1+json

Need to use libraries like Gson or Moshi

Replacing Jackson entirely

How to Add Custom Message Converters ?

Programmatic Registration

@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    List<HttpMessageConverter<?>> converters = new ArrayList<>();

    converters.add(customJacksonConverter());
    converters.add(new ProtobufHttpMessageConverter()); // if using Protobuf
    converters.addAll(restTemplate.getMessageConverters()); // preserve defaults

    restTemplate.setMessageConverters(converters);
    return restTemplate;
}

private MappingJackson2HttpMessageConverter customJacksonConverter() {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(objectMapper);
    converter.setSupportedMediaTypes(List.of(MediaType.APPLICATION_JSON));
    return converter;
}

Use Case: Supporting a Custom Media Type

public class CustomMediaTypeConverter extends AbstractHttpMessageConverter<MyCustomType> {

    public CustomMediaTypeConverter() {
        super(new MediaType("application", "vnd.company.v1+json"));
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return MyCustomType.class.equals(clazz);
    }

    @Override
    protected MyCustomType readInternal(Class<? extends MyCustomType> clazz,
                                        HttpInputMessage inputMessage) throws IOException {
        // Custom deserialization logic
    }

    @Override
    protected void writeInternal(MyCustomType myCustomType,
                                 HttpOutputMessage outputMessage) throws IOException {
        // Custom serialization logic
    }
}

Register this converter along with our RestTemplate.

Use Case: Replace Jackson with Gson

@Bean
public RestTemplate gsonRestTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    Gson gson = new GsonBuilder()
            .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
            .create();

    GsonHttpMessageConverter gsonConverter = new GsonHttpMessageConverter(gson);

    restTemplate.setMessageConverters(List.of(gsonConverter));
    return restTemplate;
}

How Converters Are Selected

When a request or response is processed:

  1. Spring inspects the Content-Type and Accept headers.

  2. It looks for the first compatible converter that supports both the Java type and media type.

  3. If none match, it throws an HttpMediaTypeNotSupportedException.

We can manipulate this behavior by adjusting:

  • Accept and Content-Type headers

  • Supported media types in our converter

  • Converter order in the list

Connection Pooling

Connection pooling is a technique used to reuse existing HTTP connections instead of opening a new connection for every request. When RestTemplate is used with its default setup (SimpleClientHttpRequestFactory), each request opens a new HTTP connection, which is expensive in high-throughput applications.

To optimize resource usage and improve performance, Spring allows RestTemplate to be configured with connection pooling using a more advanced ClientHttpRequestFactory, typically backed by Apache HttpClient or OkHttp.

Why Connection Pooling Matters ?

Reason
Benefit

Avoid overhead of creating new TCP connections

Reduces latency and CPU usage

Reuses persistent connections

Boosts throughput for HTTP/1.1 keep-alive

Handles concurrent requests efficiently

Suitable for microservices, APIs, batch jobs

Enables timeout management

Controls socket/connect/read timeouts

Provides fine-grained tuning

Max connections, eviction, retries

Default vs. Pooled

Setup
Request Factory
Connection Handling

Default

SimpleClientHttpRequestFactory

New connection per request

Pooled

HttpComponentsClientHttpRequestFactory (Apache) or OkHttp3ClientHttpRequestFactory

Reuses connections via pool

Apache HttpClient Setup for Pooling

@Bean
public RestTemplate pooledRestTemplate() {
    HttpClientConnectionManager poolingConnManager = new PoolingHttpClientConnectionManager();
    ((PoolingHttpClientConnectionManager) poolingConnManager).setMaxTotal(100);
    ((PoolingHttpClientConnectionManager) poolingConnManager).setDefaultMaxPerRoute(20);

    RequestConfig requestConfig = RequestConfig.custom()
        .setConnectTimeout(3000)
        .setSocketTimeout(5000)
        .setConnectionRequestTimeout(2000)
        .build();

    CloseableHttpClient httpClient = HttpClients.custom()
        .setConnectionManager(poolingConnManager)
        .setDefaultRequestConfig(requestConfig)
        .evictIdleConnections(30, TimeUnit.SECONDS)
        .build();

    ClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
    return new RestTemplate(factory);
}
Setting
Description

setMaxTotal(100)

Total max connections in the pool

setDefaultMaxPerRoute(20)

Max concurrent connections per route (host)

setConnectTimeout(3000)

Time to establish TCP connection

setSocketTimeout(5000)

Time waiting for data after connection

setConnectionRequestTimeout(2000)

Time waiting to obtain a connection from the pool

evictIdleConnections(...)

Closes idle connections after timeout

Using OkHttp Instead

@Bean
public RestTemplate okHttpRestTemplate(OkHttpClient okHttpClient) {
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory(okHttpClient));
}

OkHttpClient supports connection pooling and HTTP/2 by default.

Last updated