{"record":{"id":"a9ae9de6af75dd5e","repo":"spring-projects/spring-ai","slug":"both-or-none-of-sslsocketfactory-and-trustmanag","errorCode":null,"errorMessage":"Both or none of `sslSocketFactory` and `trustManager` must be set, but only one was set","messagePattern":"Both or none of `sslSocketFactory` and `trustManager` must be set, but only one was set","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/http/okhttp/SpringAiAnthropicHttpClient.java","lineNumber":645,"sourceCode":"\t\t\t\t\t: defaultDispatcherExecutor();\n\t\t\tExecutorService dispatcherExecutor = ContextExecutorService.wrap(dispatcherBase,\n\t\t\t\t\tContextSnapshotFactory.builder().build());\n\t\t\tokBuilder.dispatcher(new Dispatcher(dispatcherExecutor));\n\n\t\t\tif (this.maxIdleConnections != null && this.keepAliveDuration != null) {\n\t\t\t\tokBuilder.connectionPool(new ConnectionPool(this.maxIdleConnections, this.keepAliveDuration.toNanos(),\n\t\t\t\t\t\tTimeUnit.NANOSECONDS));\n\t\t\t}\n\t\t\telse if ((this.maxIdleConnections == null) != (this.keepAliveDuration == null)) {\n\t\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\t\"Both or none of `maxIdleConnections` and `keepAliveDuration` must be set, but only one was set\");\n\t\t\t}\n\n\t\t\tif (this.sslSocketFactory != null && this.trustManager != null) {\n\t\t\t\tokBuilder.sslSocketFactory(this.sslSocketFactory, this.trustManager);\n\t\t\t}\n\t\t\telse if ((this.sslSocketFactory == null) != (this.trustManager == null)) {\n\t\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\t\"Both or none of `sslSocketFactory` and `trustManager` must be set, but only one was set\");\n\t\t\t}\n\n\t\t\tif (this.hostnameVerifier != null) {\n\t\t\t\tokBuilder.hostnameVerifier(this.hostnameVerifier);\n\t\t\t}\n\n\t\t\tOkHttpClient okClient = okBuilder.build();\n\t\t\t// Same-host traffic: raise per-host limit to overall request limit. Matches\n\t\t\t// the SDK's tuning at the bottom of `OkHttpClient.Builder.build()`.\n\t\t\tokClient.dispatcher().setMaxRequestsPerHost(okClient.dispatcher().getMaxRequests());\n\n\t\t\tif (this.meterRegistry != null) {\n\t\t\t\tnew OkHttpConnectionPoolMetrics(okClient.connectionPool(), this.meterTags).bindTo(this.meterRegistry);\n\t\t\t}\n\n\t\t\treturn new SpringAiAnthropicHttpClient(okClient, resolvedBackend, ownsDispatcherExecutor);\n\t\t}","sourceCodeStart":627,"sourceCodeEnd":663,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/http/okhttp/SpringAiAnthropicHttpClient.java#L627-L663","documentation":"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.","triggerScenarios":"Building the client with a custom SSLSocketFactory but no X509TrustManager, or vice versa (e.g. custom trust store configured but factory omitted).","commonSituations":"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.","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)."],"exampleFix":"// before\nbuilder.sslSocketFactory(customFactory); // throws\n// after\nX509TrustManager tm = (X509TrustManager) trustManagerFactory.getTrustManagers()[0];\nbuilder.sslSocketFactory(customFactory, tm);","handlingStrategy":"validation","validationCode":"static void checkTlsConfig(SSLSocketFactory f, X509TrustManager tm) {\n    if ((f == null) != (tm == null)) {\n        throw new IllegalStateException(\"sslSocketFactory and trustManager must both be set or both null\");\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    return SpringAiAnthropicHttpClient.newBuilder()...build();\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"sslSocketFactory\")) {\n        // supply both or drop TLS customization\n    }\n    throw e;\n}","preventionTips":["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."],"tags":["configuration","tls","builder"],"backgroundTag":"conflicting-config-options","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}