1. Introduction
In this quick tutorial, we’ll explore how to configure the Jersey Client to work with HTTPS endpoints in Java. We’ll start with basic SSL configuration using JVM parameters and progress to more advanced scenarios, including custom SSL contexts and mutual TLS authentication.
2. Scenario and Setup
For our examples, we’ll test against an HTTPS endpoint that requires different levels of authentication. To simulate this, we use WireMock with custom SSL certificates. We’ll store these in a directory specified by the certs.dir system property. Next, we’ll use a shell script to generate the necessary certificates, which we’ll reference in our tests:
- server.api.service.p12: A keystore containing the server’s certificate
- trust.api.service.p12: A truststore containing trusted certificates
- client.api.service.p12: A client keystore containing the client’s certificate for mutual TLS
Finally, we’ll create a couple of variables to access our system properties:
static final String CERTS_DIR = System.getProperty("certs.dir");
static final String PASSWORD = System.getProperty("certs.password");
3. Basic HTTPS Configuration With JVM Parameters
The simplest way to configure SSL in Java is using JVM system properties. This approach is handy when we want to configure SSL globally:
@Test
void whenUsingJVMParameters_thenCorrectCertificateUsed() {
System.setProperty("javax.net.ssl.trustStore", CERTS_DIR + "/trust.api.service.p12");
System.setProperty("javax.net.ssl.trustStorePassword", PASSWORD);
Response response = ClientBuilder.newClient()
.target("https://api.service:8080/test")
.request()
.get();
assertEquals(200, response.getStatus());
}
This approach works because:
- javax.net.ssl.trustStore specifies the path to the truststore containing trusted certificates
- Jersey Client automatically uses these system properties when creating SSL connections
The advantages are that it’s simple to implement and works globally for all SSL connections in the JVM. On the other hand, the disadvantage is that the global scope might affect other parts of the application, since this affects any request, not just those made with our Jersey client.
Also, it’s less flexible for different endpoints requiring different certificates.
4. Custom SSL Context Configuration
For more control over SSL configuration, let’s create a custom context with the help of Apache HTTP SSLContextBuilder for use in our Jersey Client:
@Test
void whenUsingCustomSSLContext_thenCorrectCertificateUsed() {
SSLContext sslContext = SSLContextBuilder.create()
.loadTrustMaterial(Paths.get(CERTS_DIR + "/trust.api.service.p12"), PASSWORD)
.build();
// ...
}
First, we call loadTrustMaterial() to load the truststore containing trusted certificates. Then, we reference the SSL context in Jersey’s ClientBuilder:
Client client = ClientBuilder.newBuilder()
.sslContext(sslContext)
.build();
Finally, the sslContext() method lets us explicitly set the SSL context for this client, which affects all requests made with it:
Response response = client
.target("https://api.service:8080/test")
.request()
.get();
assertEquals(200, response.getStatus());
The main advantage is fine-grained control over SSL configuration, which can be configured differently for different endpoints.
5. Mutual TLS (mTLS) Authentication
If mutual TLS authentication is enabled on the server side, both the client and server must present certificates for authentication. This provides stronger security by ensuring both parties are authenticated. Let’s see what our setup looks like when the endpoint we need to connect to requires client authentication:
@Test
void whenUsingMutualTLS_thenCorrectCertificateUsed() {
char[] password = PASSWORD.toCharArray();
SSLContext sslContext = SSLContextBuilder.create()
.loadTrustMaterial(Paths.get(CERTS_DIR + "/trust.api.service.p12"), password)
.loadKeyMaterial(Paths.get(CERTS_DIR + "/client.api.service.p12"), password, password)
.build();
Client client = ClientBuilder.newBuilder()
.sslContext(sslContext)
.build();
Response response = client
.target("https://api.service:8080/test")
.request()
.get();
assertEquals(200, response.getStatus());
}
The only difference in this approach is setting loadKeyMaterial(), which loads the client certificate and private key for client authentication.
6. Conclusion
In this article, we’ve explored different approaches to configuring HTTPS with Jersey Client, ranging from simple JVM Parameters to mutual TLS, which offers maximum security with bidirectional authentication. Most importantly, Jersey Client provides the flexibility to handle these different scenarios effectively.
As always, the source code is available over on GitHub.