Accessing HTTPS REST Services Using Spring RestTemplate with SSL
Learn how to configure Spring RestTemplate for HTTPS with custom SSL certificates.
20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.
- ✓Java 17+
- ✓Spring Boot 3.x
- ✓Basic knowledge of REST APIs and Spring RestTemplate
- ✓Familiarity with SSL/TLS concepts (certificates, truststores)
- Use a custom
RestTemplatebean with aSSLContextthat trusts your specific certificates. - For self-signed or internal CAs, create a truststore and load it in the
SSLContext. - Never disable SSL verification in production; use
TrustAllStrategyonly for testing. - Prefer
HttpComponentsClientHttpRequestFactoryover the defaultSimpleClientHttpRequestFactoryfor better SSL control. - Use
SSLConnectionSocketFactoryfrom Apache HttpClient for fine-grained SSL configuration.
Imagine you're a security guard at a building (your Java app) and you need to let in a delivery person (an HTTPS service). Normally, you check their ID (SSL certificate) against a list of trusted IDs (Java's default truststore). But if the delivery person has a special ID from your company's own security (a self-signed certificate), you need to add that ID to your list. This tutorial shows you how to add those custom IDs so your app can securely talk to internal services.
In the real world, your Spring Boot app rarely talks only to public APIs with certificates signed by well-known Certificate Authorities. More often, you're connecting to internal services, microservices in a Kubernetes cluster, or third-party APIs that use self-signed certificates or private CAs. And that's where the default RestTemplate fails — throwing javax.net.ssl.SSLHandshakeException and leaving you scratching your head.
I've been building Spring apps since 2008, and I've debugged this exact issue in production more times than I can count. One memorable incident involved a fintech startup where a misconfigured SSL setup caused a 45-minute outage during a critical trading window. The fix was a single bean definition — but finding it cost hours of openssl s_client and Wireshark sessions.
In this article, I'll show you the battle-tested approach to configure Spring RestTemplate for HTTPS with custom SSL certificates. You'll learn how to create a custom RestTemplate bean, configure truststores, and avoid the common pitfalls that plague even experienced developers. We'll cover best practices for production, debugging techniques, and what the official docs conveniently leave out.
By the end, you'll be able to securely connect to any HTTPS service, whether it uses a public CA, a private CA, or a self-signed certificate. Let's get our hands dirty.
Understanding the SSL/TLS Handshake in RestTemplate
Before diving into configuration, let's understand what happens under the hood when RestTemplate makes an HTTPS request. The default RestTemplate uses SimpleClientHttpRequestFactory which delegates to HttpsURLConnection. This class relies on the JVM's default SSL context, which loads trust material from the cacerts file and system properties.
When you call restTemplate.getForObject("https://internal-api.company.com/data", String.class), the following happens: 1. RestTemplate creates a HttpURLConnection to the URL. 2. The JVM's HttpsURLConnection initiates an SSL handshake. 3. The server presents its certificate chain. 4. The JVM verifies the chain against the truststore (default cacerts). 5. If the chain is not trusted, it throws SSLHandshakeException.
The problem? Your internal CA's root certificate is almost certainly not in the default cacerts. And even if it were, you might need to trust only specific servers, not a whole CA.
Here's the hard truth: most teams get this wrong. They either disable SSL verification entirely (a huge security no-no) or they import every certificate into the global cacerts (which is a maintenance nightmare). The right approach is to create a dedicated truststore for your application and configure RestTemplate to use it.
Let me show you the production-grade solution.
cacerts file. It's shared across all JVM applications and can cause conflicts. Always use an application-specific truststore.Creating a Custom RestTemplate with SSL Support
Let's build a RestTemplate bean that trusts only a specific set of certificates. We'll use Apache HttpClient's SSLContextBuilder to load a truststore file (JKS or PKCS12). Here's the complete configuration:
First, add the required dependency to your pom.xml: ``xml <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.3.1</version> </dependency> ``
Now, create a @Configuration class that produces a RestTemplate bean with SSL support:
```java @Configuration public class RestTemplateConfig {
@Value("${app.truststore.location}") private Resource trustStore;
@Value("${app.truststore.password}") private String trustStorePassword;
@Bean public RestTemplate restTemplate() throws Exception { SSLContext sslContext = SSLContextBuilder.create() .loadTrustMaterial(trustStore.getFile(), trustStorePassword.toCharArray()) .build();
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory( sslContext, NoopHostnameVerifier.INSTANCE); // only if you need to disable hostname verification
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager( RegistryBuilder.<ConnectionSocketFactory>create() .register("https", sslSocketFactory) .build());
CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(connectionManager) .build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient); return new RestTemplate(factory); } } ```
Important: I used NoopHostnameVerifier.INSTANCE for simplicity, but in production you should provide a proper hostname verifier that matches your server's certificate CN or SAN. Otherwise, you're vulnerable to man-in-the-middle attacks.
A better approach is to use S or create a custom verifier that checks specific hostnames.SLConnectionSocketFactory.getDefaultHostnameVerifier()
NoopHostnameVerifier in production because "the certificate CN didn't match the hostname." They got hacked within a week. Fix the certificate, don't disable verification.Handling Self-Signed Certificates in Development
During development, you often deal with self-signed certificates. The temptation is to disable SSL verification entirely. Don't. Instead, create a development truststore and import your self-signed certificate.
Here's a quick script to generate a self-signed certificate and import it into a truststore:
```bash # Generate a self-signed certificate (valid for 365 days) keytool -genkey -alias devserver -keyalg RSA -keysize 2048 -validity 365 \ -keystore dev-keystore.jks -storepass changeit -dname "CN=localhost"
# Export the certificate keytool -export -alias devserver -keystore dev-keystore.jks -file devserver.cer
# Import into a truststore keytool -import -alias devserver -file devserver.cer -keystore dev-truststore.jks -storepass changeit -noprompt ```
Then reference this truststore in your application-dev.yml:
``yaml app: truststore: location: classpath:dev-truststore.jks password: changeit ``
This way, your development environment uses the same SSL verification logic as production — just with a different truststore. You avoid the "it works on my machine" syndrome.
What the Official Docs Won't Tell You: Spring Boot's RestTemplateBuilder does not provide a straightforward way to configure SSL. You have to drop down to the Apache HttpClient level. The docs show you how to create a RestTemplate but skip the SSL part. That's why you're reading this article.
What the Official Docs Won't Tell You
Spring's official documentation on RestTemplate is surprisingly silent on SSL configuration. They assume you're using public CAs. Here are the gotchas I've learned the hard way:
1. The Default RestTemplate Uses JVM's Truststore
You can't just set server.ssl.trust-store and expect RestTemplate to pick it up. That property only affects the embedded server (Tomcat, Jetty). For RestTemplate, you must explicitly configure the SSLContext.
2. RestTemplateBuilder Is Deceptive
RestTemplateBuilder has a requestFactory method, but it doesn't expose SSL settings. You have to build the ClientHttpRequestFactory yourself. Many developers waste hours trying to find a magic property.
3. Apache HttpClient 4.x vs 5.x
If you're using Spring Boot 3.x, it ships with Apache HttpClient 5. The API changed significantly from version 4. The old SSLConnectionSocketFactory is now in org.apache.hc.client5.http.ssl. Make sure your imports are correct.
4. Connection Pooling and SSL
When you create a custom PoolingHttpClientConnectionManager, you must register the SSL socket factory for the "https" scheme. If you forget, it falls back to the default SSL context, and your custom truststore is ignored.
5. Hostname Verification Is On by Default
If your certificate's CN doesn't match the hostname (e.g., you're using an IP address or a load balancer hostname), you'll get a SSLPeerUnverifiedException. You have two options: fix the certificate (preferred) or use a custom hostname verifier. Never use NoopHostnameVerifier in production.
6. Truststore Format Matters
While JKS is the default, PKCS12 is the modern standard. Use PKCS12 for new projects. Both work, but PKCS12 is more portable.
7. System Properties Are a Fallback
You can set -Djavax.net.ssl.trustStore=/path/to/truststore and -Djavax.net.ssl.trustStorePassword=changeit as JVM arguments. This works globally for all HTTPS connections, but it's a blunt instrument. Use it only as a fallback for non-RestTemplate calls.
server.ssl.* properties applied to RestTemplate. They don't. Always test your SSL configuration with an integration test that hits the actual service.Testing Your SSL Configuration
You can't trust your SSL setup until you've tested it. Here's how to write an integration test that verifies RestTemplate can connect to an HTTPS endpoint with your custom truststore.
Use Spring Boot's test slice annotations and a real server (or WireMock with HTTPS). I prefer using Testcontainers with a simple HTTPS server for full end-to-end testing.
```java @SpringBootTest @AutoConfigureMockMvc class RestTemplateSSLTest {
@Autowired private RestTemplate restTemplate;
@Test void shouldSuccessfullyCallHttpsEndpoint() { String url = "https://internal-api.company.com/health"; ResponseEntity<String> response = restTemplate.getForEntity(url, String.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); }
@Test void shouldFailWithUntrustedCertificate() { // Use a different RestTemplate that trusts the default cacerts RestTemplate defaultRestTemplate = new RestTemplate(); assertThatThrownBy(() -> defaultRestTemplate.getForEntity("https://internal-api.company.com/health", String.class)) .isInstanceOf(ResourceAccessException.class) .hasCauseInstanceOf(SSLHandshakeException.class); } } ```
For local testing, you can spin up a WireMock server with HTTPS and a self-signed certificate. Here's a quick example:
```java @SpringBootTest @WireMockTest(httpPort = 8443, httpsPort = 8443) class RestTemplateWireMockSSLTest {
@Autowired private RestTemplate restTemplate;
@Test void shouldCallWireMockHttps() { stubFor(get(urlEqualTo("/test")) .willReturn(aResponse().withStatus(200).withBody("OK"))); String response = restTemplate.getForObject("https://localhost:8443/test", String.class); assertThat(response).isEqualTo("OK"); } } ```
Note: WireMock's default HTTPS certificate is self-signed, so you'll need to import it into your test truststore or configure WireMock to use a trusted certificate.
Migrating from RestTemplate to WebClient for SSL
If you're starting a new project, use WebClient instead of RestTemplate. It's non-blocking, more modern, and has better SSL support. But if you're maintaining legacy code, you're stuck with RestTemplate.
However, the SSL configuration pattern is similar: you create a WebClient with a custom SslContext. Here's a quick comparison:
RestTemplate (Blocking) - Uses ClientHttpRequestFactory - Requires Apache HttpClient for SSL - Thread-per-request model
WebClient (Reactive) - Uses ClientHttpConnector (Reactor Netty or Jetty) - Built-in SSL support via SslContextBuilder - Non-blocking, event-driven
If you're migrating, here's how to configure SSL for WebClient:
```java @Bean public WebClient webClient() throws Exception { SslContext sslContext = SslContextBuilder.forClient() .trustManager(new File("/path/to/truststore.jks"), "changeit") .build();
HttpClient httpClient = HttpClient.create() .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));
return WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) .build(); } ```
Notice the simplicity? WebClient's API is cleaner. But for existing RestTemplate codebases, the Apache HttpClient approach is battle-tested and works reliably.
The 2 AM Trading Outage: A Self-Signed Certificate Disaster
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed every time the app tried to call an internal payments API.server.ssl.trust-all=true which doesn't exist in Spring Boot). They also assumed the certificate was already in the JVM's truststore because they had imported it on their local machine.RestTemplate — they were using the default one which uses the JVM's truststore. When deployed to a new environment, the certificate wasn't there.RestTemplate bean that loads this truststore into an SSLContext. 3. Used HttpComponentsClientHttpRequestFactory with a custom SSLConnectionSocketFactory that references the SSLContext. 4. Deployed the truststore as a classpath resource and referenced it via @Value. 5. Set the JVM system property javax.net.ssl.trustStore as a fallback for other HTTPS calls.- Never disable SSL verification in production — use a dedicated truststore instead.
- Always test your SSL configuration in a staging environment that mirrors production.
- Use
openssl s_client -connect host:port -showcertsto inspect the certificate chain. - Store truststores securely — treat them like passwords (use Spring Cloud Config or Vault).
- Prefer
WebClientoverRestTemplatefor new projects, but if you're stuck with RestTemplate, configure it properly.
openssl s_client -connect host:port -showcerts to get the chain. Import the root CA certificate into your truststore.-Djavax.net.debug=ssl:handshake to see detailed handshake logs.-Dhttps.protocols=TLSv1.2 or configure SSLContext with specific protocols.SimpleClientHttpRequestFactory which has limitations. Switch to HttpComponentsClientHttpRequestFactory with Apache HttpClient and explicitly set the SSLContext.openssl s_client -connect host:443 -showcerts </dev/null 2>/dev/null | sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' > server.pemkeytool -import -alias myserver -keystore mytruststore.jks -file server.pem| File | Command / Code | Purpose |
|---|---|---|
| SSLContextBuilder.java | @Component | Understanding the SSL/TLS Handshake in RestTemplate |
| RestTemplateConfig.java | @Configuration | Creating a Custom RestTemplate with SSL Support |
| application-dev.yml | app: | Handling Self-Signed Certificates in Development |
| application.properties | server.ssl.trust-store=classpath:server-truststore.jks | What the Official Docs Won't Tell You |
| RestTemplateSSLTest.java | @SpringBootTest | Testing Your SSL Configuration |
| WebClientConfig.java | @Configuration | Migrating from RestTemplate to WebClient for SSL |
Key takeaways
Interview Questions on This Topic
Explain how to configure Spring RestTemplate to connect to an HTTPS service that uses a self-signed certificate. What are the security implications?
Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.
That's Spring Boot. Mark it forged?
5 min read · try the examples if you haven't