Spring Boot: Guide to RestTemplate

Introduction

In this guide, we'll be taking a look at one of the most frequently used and well-known template in the Spring Ecosystem - known as RestTemplate, and how to use RestTemplate to send HTTP requests, pass pre-defined headers to qualified RestTemplate beans as well as how to set up mutual TLS certificate verification.

Spring is a popular and widely-spread Java framework and evolved into an entire ecosystem of modules and projects. Spring Boot has evolved from the original Spring project and helps us bootstrap standalone, production-grade applications easily. It internally takes care of a lot of boilerplate code and provides utility methods or annotations to focus mainly on the business logic of the implementation.

Spring Boot employs many Template classes such as JdbcTemplate, JmsTemplate, etc., which provide high-level simplified APIs that perform complex boilerplate tasks in the background. Similarly, RestTemplate is a central Template class that takes care of synchronous HTTP requests as a client.

It also bundles HTTP client libraries such as the JDK HttpURLConnection, Apache HttpComponents, etc.

It takes care of a lot of boilerplate code and allows us to handle common tasks with ease, such as:

  • Defining a URL object
  • Opening a connection or a connection pool
  • Defining an HTTP Request and Response Object
  • Marshal/Unmarshal HTTP Request and Response to Java Objects
  • Handling Error exceptions

RestTemplate vs WebClient

Since Spring 5, the RestTemplate class is in maintenance mode.

Spring recommends us to use the non-blocking and reactive WebClient class, which offers synchronous, asynchronous and streaming data handling scenarios instead of RestTemplate.

Though, if you're working on a project relying on a Spring version prior to 5 - you're better off using RestTemplate.

Project Setup

Let's spin up a blank Spring Boot project and go through the use-cases and approaches to using the RestTemplate class. The easiest way to start with a skeleton project is via Spring Initializr:

We will just be adding the Spring Web (we're creating a web application) and Lombok (optional boilerplate-reducing library) dependencies.

The primary focus of this guide is to cover the usage of RestTemplate and to build an HTTP client, demonstrating various HTTP requests and basic security. We'll be using a mock service that allows us to perform fictitious CRUD operations - CrudCrud. Specifically, we'll be creating a Unicorn entity, that we'll be using to send to their API.

Since we'll be sending Unicorn data over HTTP, let's create a Data Transfer Object (DTO) for it, called UnicornDTO:

// Lombok annotations for getters, setters and constructor
public class UnicornDTO {
    private String name;
    private int age;
    private String color;
}

To house the responses sent back from CrudCrud, we'll also create a UnicornResponse object to deserialize the received data:

// Lombok annotations for getters, setters and constructor
public class UnicornResponse {
    private String _id;
    private String name;
    private int age;
    private String color;
}

Note: Do you need these classes? Technically - no. You can send Unicorn data via a JSON string and receive a JSON response back. Though, it's good practice to have uniform objects to serialize and deserialize data with.

Creating a RestTemplate Bean

In any Controller, we can directly instantiate a local instance of a RestTemplate by simply instantiating the class into an object:

private RestTemplate restTemplate = new RestTemplate();

Note: It doesn't matter much if the instance is static or not, as it's thread-safe. Most commonly, RestTemplate is defined globally - though, there are arguments as to why you'd want to instantiate multiple RestTemplate instances, such as when dealing with different APIs, each having a different authentication type.

If you don't explicitly need to have multiple RestTemplate instances running, it's better to define it globally. If not, every time the controller is called by the JVM, a new instance will be created. This isn't a good design choice from the perspective of the DRY (Don't Repeat Yourself) principle.

Hence, we will create a bean at the Configuration layer and then @Autowire this bean to each Controller class to re-utilize the same template.

Additionally, this bean is also customizable, and we can set various options via the RestTemplateBuilder or RestTemplateCustomizer classes. Let's use the RestTemplateBuilder to set timeout values for connections to HTTP URLs.

To provide any configuration, we'll create a @Configuration class called, say, RestTemplateConfig and define the RestTemplate bean like this:

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder
            .setConnectTimeout(Duration.ofMillis(60000))
            .setReadTimeout(Duration.ofMillis(60000))
            .build();
    }
}

Now we can simply @Autowire the RestTemplate bean in any class to make HTTP requests:

@Autowired
private RestTemplate restTemplate;

Enabling Log Level for Debugging Messages

Developers tend to implement efficient logging pipelines to help view handshakes of messages that occur between servers. This is a gold-mine of insights and makes a huge difference when debugging. Unfortunately, Spring Boot doesn't provide an efficient way to inspect or log a JSON response body. To circumvent this, we'll try to log either HTTP headers or more interestingly, HTTP bodies as a way to have an overview of the exchange of messages.

One of the most efficient ways is to define a logger definition in application.properties file:

logging.level.org.springframework.web.client.RestTemplate=DEBUG

Note: These logs produce pretty verbose messages, it is often recommended to disable them in production as they consume a lot of memory at runtime.

If you'd like to read more about logging, read our Guide to Logging in Spring Boot.

Sending HTTP POST Requests with RestTemplate

With the proper setup finished - logging enabled, and our RestTemplate bean configured - we can go ahead and start sending HTTP requests through controllers. Let's start off with a POST request, creating a Unicorn resource via the CrudCrud API. To send POST requests, we can use either postForEntity() or postForObject() methods.

The postForEntity() Method

The postForEntity() method accepts a String denoting the URL we're sending a POST request to, the object we're sending, serialized as the HTTP body, and a ResponseType.

It returns a ResponseEntity containing the response - a generic class to encapsulate the status code of the HTTP response, the HTTP headers and an returned data. Since we've got our own UnicornResponse class, we can wrap it within a ResponseEntity:

@PostMapping(value = "/unicornsByEntity",
            consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<UnicornResponse> createUnicornByEntity(@RequestBody UnicornDTO unicornDto) throws RestClientException, JsonProcessingException {      
    return restTemplate.postForEntity(
        "https://crudcrud.com/api/72dbefb3917c4ce1b7bb17776fcf98e9/unicorns",
        unicornDto,
        UnicornResponse.class);
}

This request handler accepts a POST request and deserializes its body into a UnicornDTO object via the @RequestBody annotation, before using the autowired RestTemplate to send this object to the CrudCrud service via the postForEntity() method, packing the result in our UnicornResponse class and ResponseEntity that is finally returned.

Now, let's try making a POST request to our handler using Postman. We'll be using Postman frequently here to test the functionality of our APIs.

If you're unfamiliar with Postman - read our guide on Getting Started with Postman.

The postForObject() Method

The postForObject() works in much the same way postForEntity() does - the only difference is that postForEntity() returns a ResponseEntity, while postForObject() returns that object.

To that end, the methods behave the same, other than returning a different type.

That being said, it accepts the same arguments:

@PostMapping(value = "/unicornsByObject",
        consumes = MediaType.APPLICATION_JSON_VALUE,
        produces = MediaType.APPLICATION_JSON_VALUE)
public UnicornResponse createUnicornByObject(@RequestBody UnicornDTO unicornDto) throws RestClientException, JsonProcessingException {      
    return restTemplate.postForObject(
        "https://crudcrud.com/api/72dbefb3917c4ce1b7bb17776fcf98e9/unicorns",
        unicornDto,
        UnicornResponse.class);
}

Let's quickly try this POST call in Postman:

Sending HTTP GET Requests with RestTemplate

Following the same logic - we can send GET requests to fetch the newly created Unicorn resources. We can use the getForEntity() and getForObject() method to do this, and they follow the same conventions as the POST request counterparts.

The getForEntity() Method

The getForEntity() method returns a ResponseEntity object as a response, accepting the resource's URL and a ResponseType:

@GetMapping("/unicornsByEntity/{id}")
public ResponseEntity<String> getUnicornByIdByEntity(@PathVariable final String id) {
    return restTemplate.getForEntity(
        "https://crudcrud.com/api/72dbefb3917c4ce1b7bb17776fcf98e9/unicorns/" + id,
        String.class);
}

Let's quickly try this out in Postman:

The getForObject() Method

The getForObject() method returns a representation of the object as a response found in the ResponseType class. It expects the URL of the resource and the ResponseType's class to be passed as parameters:

@GetMapping("/unicornsByObject")
public List<UnicornResponse> getUnicornByObject() {
    return Arrays.asList(restTemplate.getForObject("https://crudcrud.com/api/72dbefb3917c4ce1b7bb17776fcf98e9/unicorns",
        UnicornResponse[].class));
}

Let's quickly try this GET call in Postman:

Sending HTTP PUT Requests with RestTemplate

For PUT requests, RestTemplate provides us with the put() method, which accepts a URL and the object we're putting into place, and doesn't return any response:

@PutMapping(value = "/unicorns/{id}", consumes = MediaType.APPLICATION_JSON_VALUE)
public void updateUnicorn(@PathVariable final String id, @RequestBody UnicornDTO unicornDto) {
    restTemplate.put(
        "https://crudcrud.com/api/72dbefb3917c4ce1b7bb17776fcf98e9/unicorns/" + id,
        unicornDto);
}

Let's fire this request via Postman as well:

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

Sending HTTP DELETE Requests with RestTemplate

For DELETE requests, we can use the delete() method. It deletes the resource targeted by a URL with an ID being passed as the parameter. Thus, it just expects the URL to be passed and doesn't return any response:

@DeleteMapping("/unicorns/{id}")
public void deleteteUnicornByIdByEntity(@PathVariable final String id) {
    restTemplate.delete("https://crudcrud.com/api/72dbefb3917c4ce1b7bb17776fcf98e9/unicorns/" + id);
}

Let's send a DELETE request via Postman:

The exchange() Method

A method worthy of notice is the exchange() method. It's a generalization of any HTTP exchange.

This means that it can be used for any HTTP call and can be a generic alternative to any of the previous calls. The exchange() method returns a ResponseEntity and accepts a RequestEntity - which is constituted by an HTTP method, URL, headers and body - and a ResponseType.

Note: You can also pass in the constituent parts individually.

The exchange() method expects a RequestEntity or a URL, a proper HTTP method, an HTTP entity serialized into a body and a ResponseType object to be passed as parameters.

Let's create a PUT request and send it to the CrudCrud service, using the exchange() method instead:

@PutMapping(value = "/unicorns/{id}", consumes = MediaType.APPLICATION_JSON_VALUE)
public void updateUnicorn(@PathVariable final String id, @RequestBody UnicornDTO unicornDto) {
    restTemplate.exchange(
        "https://crudcrud.com/api/72dbefb3917c4ce1b7bb17776fcf98e9/unicorns/" + id,
        HttpMethod.PUT,
        new HttpEntity<>(unicornDto),
        Void.class);
}

And let's fire the request via Postman:

Passing Predefined Headers Using RestTemplate

We often face situations where we might need to pass pre-defined request headers for certain APIs. These headers mostly resemble Authentication or Authorization key-value pairs or cookies. They can also be used to set acceptable content types or formats to consume the response data.

We can try passing Basic Authentication tokens or JWT Bearer tokens as headers while calling an API via the RestTemplate class.

In this guide, we will try calling pre-hosted APIs from the COVID-19 Rapid API portal. This API requires you to mandatorily pass headers like "X-RapidAPI-Key" or "X-RapidAPI-Host" to get the latest total Covid-19 records.

Update: The original API used for this article no longer exists. You can replace it with one of the many COVID-19 APIs hosted by Rapid API. Just change the target URL to the new API. Your output will look different than what is shown here, but the concept and purpose remains the same.

To pass these headers as part of all methods that use our RestTemplate instance, we'll define a dedicated bean overloaded with an Interceptor implementation. Interceptors are used to intercept and add custom headers, log HTTP requests or responses, or deny various kinds of requests, when they're being sent or received.

A common interceptor is the ClientHttpRequestInterceptor interface, and we'll implement it to intercept any header key-value pair being passed to our RestTemplate:

public class HttpClientRequestInterceptor implements ClientHttpRequestInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientRequestInterceptor.class);
    
    private final String headerName;

    private final String headerValue;

    public HttpClientRequestInterceptor(String headerName, String headerValue) {
        this.headerName = headerName;
        this.headerValue = headerValue;
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        // Add Auth Headers
        request.getHeaders().set(headerName, headerValue);
        
        // Add logger info settings
        logRequestDetails(request);

        return execution.execute(request, body);
    }

    // Adding custom loggers
    private void logRequestDetails(HttpRequest request) {
        LOGGER.info("Request Headers: {}", request.getHeaders());
        LOGGER.info("Request Method: {}", request.getMethod());
        LOGGER.info("Request URI: {}", request.getURI());
    }
}

Now, we can use this interceptor to pass mandatory Rapid API headers to our RestTemplate bean when required. In such cases, RestTemplate will be pre-constructed with these headers.

We can fetch any environment variables defined in the application.properties file using the @Value annotation, which is handy for our key-value pairs. Then, we can create a custom RestTemplate bean and annotate it with a Bean Name. This allows us to use the @Qualifier annotation to point to that specific bean, rather than the default bean that doesn't have these headers.

If you want to learn more about annotations such as these, read our guide to Spring Annotations: Core Framework Annotations.

We'll further be using the HttpComponentsClientHttpRequestFactory to set a timeout settings. This instance is passed into the RestTemplate constructor and can be used to specify settings if you don't already have them from a global, autowired bean.

Finally, using our interceptor, we can pass in the key-value pairs into the headers of each request we send via the RestTemplate:

@Value("${api.rapid-api.host}")
private String rapidApiHost;

@Value("${api.rapid-api.key}")
private String rapidApiKey;

@Bean(name = "rapidApiRestTemplate")
public RestTemplate rapidApiRestTemplate()
    throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
    HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();

    // Connect timeout
    clientHttpRequestFactory.setConnectTimeout(60000);

    // Read timeout
    clientHttpRequestFactory.setReadTimeout(60000);

    RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);

    // Interceptor section
    List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
    if (CollectionUtils.isEmpty(interceptors)) {
        interceptors = new ArrayList<ClientHttpRequestInterceptor>();
    }
    interceptors.add(new HttpClientRequestInterceptor("x-rapidapi-key", rapidApiKey));
    interceptors.add(new HttpClientRequestInterceptor("x-rapidapi-host", rapidApiHost));
    restTemplate.setInterceptors(interceptors);

    return restTemplate;
}

The application.properties has the appropriate key-value pairs:

api.rapid-api.host=covid-19-data.p.rapidapi.com
api.rapid-api.key=d8ce580441msh8a191819cd7754ap111a26jsnd6df9268e190

Now, we can call this specific bean, rather than our regular RestTemplate via the @Qualifier annotation. Calling the bean name "rapidApiRestTemplate" will autowire this configuration of RestTemplate. Then, when we'd like to send a request to an endpoint, they'll include the pre-defined headers:

@RestController
@RequestMapping("/api/covid")
public class CovidReportController {

    @Autowired
    @Qualifier("rapidApiRestTemplate")
    private RestTemplate restTemplate;
    
    @GetMapping("/getAllLatest")
    public ResponseEntity<String> getAllCovidCount() {
        return restTemplate.getForEntity("https://covid-19-data.p.rapidapi.com/totals?format=json",
            String.class);
    }
}

Now when we try calling this API in Postman, it returns the proper response since our calls had the appropriate headers:

Mutual TLS Certificate Verification with RestTemplate

Until now, we've been trying to fetch data mostly from openly available websites, even though they're hosted on secure domains. In practice, when we try integrating with much more secure endpoints like the ones that have been secured using one-way or 2-way mutual authentication, we will get into the real challenge as we need to make a lot of secured handshakes before we can get hold of the actual data.

So, let's look into one of the scenarios where we need to create a Keystore and a Truststore to encrypt and bundle the server certificates and validate them every time we try to access the endpoints.

Note: This guide assumes you are familiar with the basic concepts of Keystores, Truststores, and SSL/TLS layers.

As a quick refresher - Keystores and Truststores are required for SSL communication and verification. A Keystore is used to store the server's private key and own identity certificate whereas a Truststore is used for storage of certificates from the trusted Certificate Authority(CA).

Keystores are used by the server for communications, while Truststores are used to verify the server certificates before communication, to allow handshakes.

Let's just quickly spin up a keystore and truststore with a simple Bash script:

#!/bin/bash

echo "generating key" 
openssl genrsa -des3 -out server.key 1024

echo "generating csr"
openssl req -new -key server.key -out server.csr

echo "generating self-signed certificate signed by key"
openssl x509 -req -days 3650 -in server.csr -signkey server.key -out server.crt

echo "generating key-store containing self-signed certificate signed by key"
openssl pkcs12 -export -in server.crt -inkey server.key -name "server" -out keystore.p12

echo "generating trust-store containing self-signed certificate signed by key"
openssl pkcs12 -export -in server.crt -inkey server.key -name "server" -out truststore.jks

While running this script, you'll be prompted for details such as the pass-phrase, common name, organizational name, etc., to create the certificate. At the end of this script, you can see a keystore.p12 and truststore.jks. The .p12 file denotes PKCS12 format whereas .jks file denotes JKS(Java KeyStore) format.

Let's map these files and file types in our application.properties:

application.protocol=TLSv1.2
application.keystore.path=/path/to/keystore.p12
application.keystore.type=PKCS12
application.keystore.password=passPhrase
application.truststore.path=/path/to/truststore.jks
application.truststore.type=JKS
application.truststore.password=passPhrase

The Keystore and Truststore password are the pass-phrase that has been provided while creating the server certificates and keys. Now we need to define another separate RestTemplate bean and map these variables for a successful handshake.

We'll need to add another library, HttpClient, to our project, which is not part of Spring Initializr, through Maven or Gradle, to provide SSL configurations.

If you're using Maven, add the httpclient dependency to your pom.xml:

<!--HTTP Client-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

Or, if you're using Gradle:

implementation group: 'org.apache.httpcomponents', name: 'httpclient'

Since we will also be using HttpClient library, let's define the log level for the client:

logging.level.org.apache.http=DEBUG
logging.level.httpclient.wire=DEBUG

Note: These logs are, as previous ones, verbose. Avoid them in production since they can be a memory-hog.

We'll be creating a Keystore and Truststore instance each, and add them to the SSLConnectionSocketFactory for verification. Additionally, we'll use a HostnameVerifier so that the factory can check/match the Common Name defined in the certificate with the SSL Session made with the actual domain:

@Value("${application.keystore.path}")
private String keystorePath;

@Value("${application.keystore.type}")
private String keystoreType;

@Value("${application.keystore.password}")
private String keystorePassword;

@Value("${application.truststore.path}")
private String truststorePath;

@Value("${application.truststore.type}")
private String truststoreType;

@Value("${application.truststore.password}")
private String truststorePassword;

@Value("${application.protocol}")
private String protocol;

@Bean(name = "mutualAuthRestTemplate")
public RestTemplate mutualAuthRestTemplate() throws KeyStoreException, NoSuchAlgorithmException,
    CertificateException, IOException, KeyManagementException {

    // Load Keystore
    final KeyStore keystore = KeyStore.getInstance(keystoreType);
    try (InputStream in = new FileInputStream(keystorePath)) {
        keystore.load(in, keystorePassword.toCharArray());
    }

    // Load Truststore
    final KeyStore truststore = KeyStore.getInstance(truststoreType);
    try (InputStream in = new FileInputStream(truststorePath)) {
        truststore.load(in, truststorePassword.toCharArray());
    }

    // Build SSLConnectionSocket to verify certificates in truststore
    final SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(new SSLContextBuilder()
        .loadTrustMaterial(truststore, new TrustSelfSignedStrategy()).setProtocol(protocol).build(),
        new HostnameVerifier() {
            HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();

            @Override
            public boolean verify(String hostname, SSLSession session) {
                return hostnameVerifier.verify(hostname, session);
            }
    });

    CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build();
    return new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpclient));
}

Now we can use this RestTemplate Bean to call a secured mutual auth Rest API using TLS Socket layer. We are using TLSv1.2 as a default protocol in the implementation.

Conclusion

In this guide, we explored the RestTemplate class of the Spring Ecosystem. We've taken a look at how we can utilize it to send GET, POST, DELETE and PUT requests, as well as the generic exchange() method.

We have also overloaded Apache's HTTP Client utility methods into a RestTemplate bean and created a secure socket layer to create an SSL handshake with different endpoints.

You can find the full source code on GitHub.

Last Updated: October 14th, 2023
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

Arpendu Kumar GaraiAuthor

Full-Stack developer with deep knowledge in Java, Microservices, Cloud Computing, Big Data, MERN, Javascript, Golang, and its relative frameworks. Besides coding and programming, I am a big foodie, love cooking, and love to travel.

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms