spring-projects/spring-ai · error · IllegalStateException

Both or none of `maxIdleConnections` and `keepAliveDuration`

Error message

Both or none of `maxIdleConnections` and `keepAliveDuration` must be set, but only one was set

What it means

The SpringAiOpenAiHttpClient builder treats maxIdleConnections and keepAliveDuration as a pair: both or neither must be set to configure a custom connection pool. Setting only one is an invalid builder state and build() throws IllegalStateException.

Source

Thrown at models/spring-ai-openai/src/main/java/org/springframework/ai/openai/http/okhttp/SpringAiOpenAiHttpClient.java:575

							toHttpResponse(response));
					return authed.map(req -> toRequestComputeUrl(req, null)).orElse(null);
				});
			}

			ExecutorService userDispatcherExecutor = this.dispatcherExecutorService;
			boolean ownsDispatcherExecutor = userDispatcherExecutor == null;
			ExecutorService dispatcherBase = (userDispatcherExecutor != null) ? userDispatcherExecutor
					: 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()`.

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Set both values together: maxIdleConnections(n).keepAliveDuration(Duration.ofMinutes(5)).
  2. Remove both settings to fall back to OkHttp's default connection pool.
  3. Add a startup self-check that builds the client once and fails fast with a clear message.

Example fix

// before
SpringAiOpenAiHttpClient.builder().maxIdleConnections(10).build();
// after
SpringAiOpenAiHttpClient.builder()
    .maxIdleConnections(10)
    .keepAliveDuration(Duration.ofMinutes(5))
    .build();
Defensive patterns

Strategy: validation

Validate before calling

if ((maxIdleConnections == null) != (keepAliveDuration == null)) {
    throw new IllegalStateException("Set both maxIdleConnections and keepAliveDuration, or neither");
}

Try / catch

try {
    client = SpringAiOpenAiHttpClient.builder()
        .maxIdleConnections(cfg.idle())
        .keepAliveDuration(cfg.keepAlive())
        .build();
} catch (IllegalStateException e) {
    throw new ClientConfigurationException("Invalid pool configuration: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: Calling SpringAiOpenAiHttpClient.builder().maxIdleConnections(5).build() without keepAliveDuration (or the reverse), then invoking build() at SpringAiOpenAiHttpClient.java:575.

Common situations: Copy-pasted tuning snippets that set only the pool size, config binding that populates one property but not its companion, or migration from a client that accepted a lone pool-size setting.

Related errors


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