Spring Boot Mutual TLS Tutorial: Secure REST Calls with RestTemplate (Step-by-Step Guide)

3 min read

Spring Boot Mutual TLS Tutorial: Secure REST Calls with RestTemplate (Step-by-Step Guide)

1. Introduction

Mutual TLS (mTLS) is an authentication mechanism that ensures both the client and the server verify each other’s identities during HTTPS communication. Unlike one-way TLS, where only the server presents a certificate, mTLS requires the client to present its own certificate as well. This adds an extra layer of trust, making it ideal for secure microservices communication, financial APIs, healthcare systems, and other sensitive environments.

In a Spring Boot application, one of the common ways to make outbound HTTPS calls is by using RestTemplate. In this article, we will walk through how to configure RestTemplate to communicate with a server using mTLS, focusing on a clean, production-grade setup.

2. Problem

Many Spring Boot applications need to securely call external partner APIs, internal microservices, or private enterprise endpoints that require mutual TLS. Developers often face the following challenges:

  • How to configure the keystore and truststore correctly.
  • How to attach the client certificate to outgoing HTTPS requests.
  • How to load the certificates at startup and bind them to a custom RestTemplate.
  • How to avoid common pitfalls such as incorrect key format or SSL handshake failures.

Without proper configuration, the application will fail with SSL handshake errors such as:

javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate

3. Solution

To successfully implement mTLS with RestTemplate in Spring Boot, we need the following:

  • A keystore containing the client’s private key and certificate.
  • A truststore containing the server’s certificate(s).
  • A custom SSLContext configured with both stores.
  • A RestTemplate using this SSLContext.
  • Externalized configuration via application.yml.

We will use the following Spring annotations: @Configuration, @Bean, @Value, @Service, and @Autowired.

4. Implementation

4.1 Application Properties

mtls:
  keystore:
    path: classpath:certs/client-keystore.p12
    password: changeit
    type: PKCS12
  truststore:
    path: classpath:certs/client-truststore.p12
    password: changeit
    type: PKCS12

external:
  secure-api-url: https://partner-secure-api.example.com/secure-endpoint

4.2 SSL Configuration Class

package com.example.mtls.config;

import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.security.KeyStore;

@Configuration
public class SslConfig {

    @Value("${mtls.keystore.path}")
    private Resource keystorePath;

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

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

    @Value("${mtls.truststore.path}")
    private Resource truststorePath;

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

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

    @Bean
    public RestTemplate mtlsRestTemplate() throws Exception {

        KeyStore keyStore = KeyStore.getInstance(keystoreType);
        try (InputStream in = keystorePath.getInputStream()) {
            keyStore.load(in, keystorePassword.toCharArray());
        }

        KeyStore trustStore = KeyStore.getInstance(truststoreType);
        try (InputStream in = truststorePath.getInputStream()) {
            trustStore.load(in, truststorePassword.toCharArray());
        }

        SSLContext sslContext = SSLContextBuilder.create()
                .loadKeyMaterial(keyStore, keystorePassword.toCharArray())
                .loadTrustMaterial(trustStore, null)
                .build();

        CloseableHttpClient httpClient = HttpClients.custom()
                .setSSLContext(sslContext)
                .build();

        HttpComponentsClientHttpRequestFactory factory =
                new HttpComponentsClientHttpRequestFactory(httpClient);

        return new RestTemplate(factory);
    }
}

Notes:
loadKeyMaterial() loads the client certificate and private key.
loadTrustMaterial() loads the trusted CA certificate.
– The resulting SSLContext supports full mutual TLS handshake.

4.3 Service Class Using the Mutual TLS RestTemplate

package com.example.mtls.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class SecureApiClient {

    @Autowired
    private RestTemplate mtlsRestTemplate;

    @Value("${external.secure-api-url}")
    private String secureApiUrl;

    public String callSecureEndpoint() {
        return mtlsRestTemplate.getForObject(secureApiUrl, String.class);
    }
}

4.4 Controller to Trigger Request

package com.example.mtls.controller;

import com.example.mtls.service.SecureApiClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SecureController {

    private final SecureApiClient apiClient;

    public SecureController(SecureApiClient apiClient) {
        this.apiClient = apiClient;
    }

    @GetMapping("/test-mtls")
    public String testMtls() {
        return apiClient.callSecureEndpoint();
    }
}

4.5 Directory Layout

src/main/resources/certs/client-keystore.p12
src/main/resources/certs/client-truststore.p12
src/main/resources/application.yml
src/main/java/com/example/mtls/config/SslConfig.java
src/main/java/com/example/mtls/service/SecureApiClient.java
src/main/java/com/example/mtls/controller/SecureController.java

5. Conclusion

Mutual TLS provides strong authentication and encryption for secure system-to-system communication. In Spring Boot, configuring mTLS with RestTemplate requires correctly loading both a client keystore and a truststore, then binding them to a custom SSLContext. With the configuration shown in this article, your application can safely connect to APIs that require certificate-based authentication.

This approach is production-ready, customizable, and works well for internal microservices and partner API integrations.

🤞 Never miss a story from us, get weekly updates to your inbox!

Leave a Reply

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