| Component | Role |
SSLContext | Factory for SSLSocketFactory and SSLServerSocketFactory; holds key material and trust anchors |
KeyStore | Repository of certificates and private keys (JKS, PKCS12) |
KeyManager | Selects which client certificate to present during handshake |
TrustManager | Decides whether to trust the server's certificate chain |
SSLSocket / SSLServerSocket | TLS-wrapped stream sockets |
SSLSession | Negotiated cipher suite, protocol version, peer certificates |
HostnameVerifier | Validates the server's hostname against the certificate CN/SAN |
For connections to public HTTPS servers the default SSLContext trusts the JVM's built-in CA bundle — no configuration needed.
import javax.net.ssl.*;
import java.io.*;
// Client connecting to a public server
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
try (SSLSocket socket = (SSLSocket) factory.createSocket("example.com", 443)) {
socket.startHandshake(); // negotiate TLS
SSLSession session = socket.getSession();
System.out.println("Protocol : " + session.getProtocol()); // TLSv1.3
System.out.println("Cipher : " + session.getCipherSuite());
System.out.println("Peer cert: " + session.getPeerCertificates()[0]);
// use like a normal socket
var out = new PrintWriter(socket.getOutputStream(), true);
var in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out.println("GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n");
System.out.println(in.readLine()); // HTTP/1.1 200 OK
}
When connecting to servers with self-signed or private CA certificates you must build a custom SSLContext.
import java.security.*;
import javax.net.ssl.*;
import java.io.*;
public SSLContext buildSslContext(String keystorePath, char[] keystorePassword,
String truststorePath, char[] truststorePassword)
throws Exception {
// 1. Load the KeyStore (contains our private key + certificate chain)
KeyStore keyStore = KeyStore.getInstance("PKCS12");
try (InputStream ks = new FileInputStream(keystorePath)) {
keyStore.load(ks, keystorePassword);
}
// 2. Load the TrustStore (contains trusted CA certificates)
KeyStore trustStore = KeyStore.getInstance("JKS");
try (InputStream ts = new FileInputStream(truststorePath)) {
trustStore.load(ts, truststorePassword);
}
// 3. Initialise KeyManager and TrustManager
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keystorePassword);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
// 4. Build the SSLContext
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return ctx;
}
// Use the custom context
SSLContext ctx = buildSslContext("client.p12", "keystorePass".toCharArray(),
"truststore.jks", "trustPass".toCharArray());
SSLSocketFactory factory = ctx.getSocketFactory();
try (SSLSocket socket = (SSLSocket) factory.createSocket("internal-server", 8443)) {
socket.startHandshake();
// ... read/write
}
SSLContext ctx = buildSslContext("server.p12", "pass".toCharArray(),
"truststore.jks", "pass".toCharArray());
SSLServerSocketFactory ssf = ctx.getServerSocketFactory();
try (SSLServerSocket server = (SSLServerSocket) ssf.createServerSocket(8443)) {
// require TLS 1.2+
server.setEnabledProtocols(new String[]{"TLSv1.2", "TLSv1.3"});
System.out.println("TLS server on port 8443");
while (true) {
SSLSocket client = (SSLSocket) server.accept();
new Thread(() -> {
try (client;
var in = new BufferedReader(new InputStreamReader(client.getInputStream()));
var out = new PrintWriter(client.getOutputStream(), true)) {
out.println("Hello over TLS!");
System.out.println("Client: " + in.readLine());
} catch (IOException e) { e.printStackTrace(); }
}).start();
}
}
Hostname verification ensures the server's certificate matches the hostname you are connecting to — it prevents man-in-the-middle attacks even when the certificate is otherwise valid.
SSLSocket does not perform hostname verification by default — you must enable it explicitly using SSLParameters:
SSLSocket socket = (SSLSocket) factory.createSocket("myserver.example.com", 443);
// Enable HTTPS-style hostname verification (checks CN and SANs)
SSLParameters params = socket.getSSLParameters();
params.setEndpointIdentificationAlgorithm("HTTPS");
socket.setSSLParameters(params);
socket.startHandshake(); // throws SSLHandshakeException if hostname doesn't match
Never disable hostname verification in production code. If you see code like setHostnameVerifier((h, s) -> true), it makes the connection vulnerable to MITM attacks.
In mTLS both the client and server authenticate with certificates. The server calls setNeedClientAuth(true) and the client must have a key loaded in its KeyManager.
// Server: require client certificate
try (SSLServerSocket server = (SSLServerSocket) ssf.createServerSocket(8443)) {
server.setNeedClientAuth(true); // reject connections without a valid client cert
SSLSocket client = (SSLSocket) server.accept();
client.startHandshake();
// inspect client identity
java.security.cert.Certificate[] certs = client.getSession().getPeerCertificates();
System.out.println("Client cert: " + certs[0]);
}
// Client: must have a private key + certificate in its KeyStore
SSLContext ctx = buildSslContext("client.p12", "clientPass".toCharArray(),
"server-ca.jks", "caPass".toCharArray());
SSLSocketFactory sf = ctx.getSocketFactory();
SSLSocket socket = (SSLSocket) sf.createSocket("server", 8443);
socket.startHandshake(); // presents client certificate automatically
SSLSocket socket = (SSLSocket) factory.createSocket("host", 443);
// Restrict to TLS 1.3 only
socket.setEnabledProtocols(new String[]{"TLSv1.3"});
// List available cipher suites
System.out.println(Arrays.toString(socket.getSupportedCipherSuites()));
// Enable only strong cipher suites
socket.setEnabledCipherSuites(new String[]{
"TLS_AES_256_GCM_SHA384",
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256"
});
| Protocol | Recommendation |
| TLSv1.3 | ✅ Prefer — faster handshake, stronger security, no weak ciphers |
| TLSv1.2 | ✅ Acceptable when TLS 1.3 not supported by peer |
| TLSv1.1 | ❌ Deprecated (RFC 8996) — disable explicitly |
| TLSv1.0 | ❌ Deprecated (RFC 8996) — disable explicitly |
| SSLv3 | ❌ Broken (POODLE) — never use |
Quick commands to create a self-signed server certificate for local testing:
# Generate a self-signed server key + cert in PKCS12 format
keytool -genkeypair -alias server -keyalg RSA -keysize 2048 \
-validity 365 -dname "CN=localhost,O=Test,C=US" \
-keystore server.p12 -storetype PKCS12 -storepass changeit
# Export the server cert as PEM for use as a trust anchor
keytool -exportcert -alias server -keystore server.p12 \
-storepass changeit -rfc -file server-cert.pem
# Import server cert into client's truststore
keytool -importcert -alias server-ca -file server-cert.pem \
-keystore client-truststore.jks -storepass changeit -noprompt
For local development with self-signed certs, set the system property -Djavax.net.debug=ssl:handshake to see the full TLS handshake log — it shows which certificates are presented and which ciphers are negotiated.