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

  1. Always set both: .sslSocketFactory(socketFactory, trustManager).
  2. If you only meant to trust extra CAs, build the factory and trust manager from the same SSLContext/TrustManagerFactory pair.
  3. Remove both to use the system default TLS configuration.
  4. 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

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


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/a9ae9de6af75dd5e. Report an issue: GitHub.