spring-projects/spring-ai · error · IllegalStateException
Both or none of `sslSocketFactory` and `trustManager` must b
Error message
Both or none of `sslSocketFactory` and `trustManager` must be set, but only one was set
What it means
During build(), the client Builder checks that TLS customization is complete: sslSocketFactory and trustManager must be provided together, because OkHttp's sslSocketFactory(SSLSocketFactory, X509TrustManager) requires both. Supplying only one throws IllegalStateException.
Source
Thrown at models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/http/okhttp/SpringAiAnthropicHttpClient.java:645
: defaultDispatcherExecutor();
ExecutorService dispatcherExecutor = ContextExecutorService.wrap(dispatcherBase,
ContextSnapshotFactory.builder().build());
okBuilder.dispatcher(new Dispatcher(dispatcherExecutor));
if (this.maxIdleConnections != null && this.keepAliveDuration != null) {
okBuilder.connectionPool(new ConnectionPool(this.maxIdleConnections, this.keepAliveDuration.toNanos(),
TimeUnit.NANOSECONDS));
}
else if ((this.maxIdleConnections == null) != (this.keepAliveDuration == null)) {
throw new IllegalStateException(
"Both or none of `maxIdleConnections` and `keepAliveDuration` must be set, but only one was set");
}
if (this.sslSocketFactory != null && this.trustManager != null) {
okBuilder.sslSocketFactory(this.sslSocketFactory, this.trustManager);
}
else if ((this.sslSocketFactory == null) != (this.trustManager == null)) {
throw new IllegalStateException(
"Both or none of `sslSocketFactory` and `trustManager` must be set, but only one was set");
}
if (this.hostnameVerifier != null) {
okBuilder.hostnameVerifier(this.hostnameVerifier);
}
OkHttpClient okClient = okBuilder.build();
// Same-host traffic: raise per-host limit to overall request limit. Matches
// the SDK's tuning at the bottom of `OkHttpClient.Builder.build()`.
okClient.dispatcher().setMaxRequestsPerHost(okClient.dispatcher().getMaxRequests());
if (this.meterRegistry != null) {
new OkHttpConnectionPoolMetrics(okClient.connectionPool(), this.meterTags).bindTo(this.meterRegistry);
}
return new SpringAiAnthropicHttpClient(okClient, resolvedBackend, ownsDispatcherExecutor);
}View on GitHub (pinned to 98a7beda4f)
Solutions
- Always set both: .sslSocketFactory(socketFactory, trustManager).
- If you only meant to trust extra CAs, build the factory and trust manager from the same SSLContext/TrustManagerFactory pair.
- Remove both to use the system default TLS configuration.
- Verify your config/beans supply the pair atomically (a single SslBundle or TLS config object).
Example fix
// before builder.sslSocketFactory(customFactory); // throws // after X509TrustManager tm = (X509TrustManager) trustManagerFactory.getTrustManagers()[0]; builder.sslSocketFactory(customFactory, tm);
Defensive patterns
Strategy: validation
Validate before calling
static void checkTlsConfig(SSLSocketFactory f, X509TrustManager tm) {
if ((f == null) != (tm == null)) {
throw new IllegalStateException("sslSocketFactory and trustManager must both be set or both null");
}
} Try / catch
try {
return SpringAiAnthropicHttpClient.newBuilder()...build();
} catch (IllegalStateException e) {
if (e.getMessage().contains("sslSocketFactory")) {
// supply both or drop TLS customization
}
throw e;
} Prevention
- Derive SSLSocketFactory and X509TrustManager from one SSLContext/TrustManagerFactory and pass them together.
- Keep TLS customization in a single configuration class/bean.
- Test client construction in CI so half-configured builders fail early.
When it happens
Trigger: Building the client with a custom SSLSocketFactory but no X509TrustManager, or vice versa (e.g. custom trust store configured but factory omitted).
Common situations: Setting up mTLS or a custom CA trust store with an incomplete snippet; Spring property binding that populates only one of the two TLS beans; security hardening changes that replaced the factory without the trust manager.
Related errors
- Both or none of `sslSocketFactory` and `trustManager` must b
- MessageEndpoint must be set
- DataSource must be set (either via dataSource() or jdbcTempl
- Both or none of `maxIdleConnections` and `keepAliveDuration`
- Both or none of `maxIdleConnections` and `keepAliveDuration`
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/a9ae9de6af75dd5e.
Report an issue: GitHub.