apple/pkl · error · SSLHandshakeException
errorSslHandshake
errorSslHandshake
Error message
errorSslHandshake: ${host}: ${reason} What it means
JdkHttpClient.send catches SSLHandshakeException and rethrows an SSLHandshakeException with code 'errorSslHandshake' including the host and the root cause reason. It means the TLS handshake with the server failed — typically certificate trust, protocol, or cipher problems.
Solutions
- Add the server's (or corporate CA's) certificate to the evaluator's trusted certificates via PklEvaluatorSettings certificateFiles/certificateBytes
- Read the 'reason' in the message: fix expired/mismatched certificates on the server if you control it
- If behind a TLS-intercepting proxy, import the proxy's root CA into the trust settings
- Check TLS version compatibility (force TLS 1.2/1.3 as needed)
Example fix
// before: evaluator without custom CA, self-signed internal host fails
var eval = EvaluatorBuilder.preconfigured().build();
// after
var settings = new PklEvaluatorSettings();
settings.setCertificateFiles(List.of(Path.of("/etc/certs/internal-ca.pem")));
var eval = EvaluatorBuilder.preconfigured().applyFromSettings(settings).build(); Defensive patterns
Strategy: fallback
Validate before calling
// verify the server certificate is trusted before evaluating
Process p = new ProcessBuilder("openssl", "s_client", "-connect", host + ":443", "-servername", host).start();
// inspect the presented chain; ensure the CA is in your evaluator's certificateFiles Try / catch
try {
return client.send(request, handler);
} catch (SSLHandshakeException e) {
if (e.getMessage().startsWith("errorSslHandshake")) {
// rebuild client with the CA from the message's reason (e.g. add CA cert to settings) and retry once
}
} Prevention
- Distribute your organization's root CA via PklEvaluatorSettings certificateFiles/certificateBytes
- Monitor certificate expiry for internal registries your projects import from
- Test TLS connectivity from the same environment where evaluations run
- Account for TLS-intercepting proxies by trusting their root CA explicitly
When it happens
Trigger: Any HTTPS request where the TLS handshake fails: self-signed or unknown CA certificate, expired server certificate, hostname mismatch, TLS version/cipher incompatibility, corporate TLS-intercepting proxy.
Common situations: Internal CA not in the trust store, MITM proxies at work, dev servers with self-signed certs, custom certificates not passed via evaluator settings (certificateFiles/certificateBytes).
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08).
Data as JSON: /api/errors/090fb7b6d0a39e46.
Report an issue: GitHub.
Appendix: source
Thrown at pkl-core/src/main/java/org/pkl/core/http/JdkHttpClient.java:103
.proxy(proxySelector)
.followRedirects(Redirect.NEVER)
.build();
}
@Override
public <T> HttpResponse<T> send(
HttpRequest request,
BodyHandler<T> responseBodyHandler,
HttpRequestChecker httpRequestChecker)
throws IOException {
try {
return underlying.send(request, responseBodyHandler);
} catch (ConnectException e) {
// original exception has no message
throw new ConnectException(
ErrorMessages.create("errorConnectingToHost", request.uri().getHost()));
} catch (SSLHandshakeException e) {
throw new SSLHandshakeException(
ErrorMessages.create(
"errorSslHandshake", request.uri().getHost(), Exceptions.getRootReason(e)));
} catch (SSLException e) {
throw new SSLException(Exceptions.getRootReason(e));
} catch (InterruptedException e) {
// next best thing after letting (checked) InterruptedException bubble up
Thread.currentThread().interrupt();
throw new IOException(e);
}
}
@Override
public void close() {
try {
closeMethod.invoke(underlying);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable t) {View on GitHub (pinned to f3efcbfc9b)