@@ -53,6 +53,12 @@ public final class ApiServerConfig {
5353 private final Set <String > allowedOrigins ;
5454 private final int threadPoolSize ;
5555 private final int maxThreadPoolSize ;
56+ private final boolean enableTls ;
57+ private final String keystorePath ;
58+ private final String keystorePassword ;
59+ private final String keyAlias ;
60+ private final String [] enabledProtocols ;
61+ private final String [] enabledCiphers ;
5662
5763 private ApiServerConfig (Builder builder ) {
5864 this .port = builder .port ;
@@ -66,6 +72,12 @@ private ApiServerConfig(Builder builder) {
6672 : Collections .emptySet ();
6773 this .threadPoolSize = builder .threadPoolSize ;
6874 this .maxThreadPoolSize = builder .maxThreadPoolSize ;
75+ this .enableTls = builder .enableTls ;
76+ this .keystorePath = builder .keystorePath ;
77+ this .keystorePassword = builder .keystorePassword ;
78+ this .keyAlias = builder .keyAlias ;
79+ this .enabledProtocols = builder .enabledProtocols ;
80+ this .enabledCiphers = builder .enabledCiphers ;
6981 }
7082
7183 /**
@@ -140,6 +152,60 @@ public int getMaxThreadPoolSize() {
140152 return maxThreadPoolSize ;
141153 }
142154
155+ /**
156+ * Checks if TLS/HTTPS is enabled.
157+ *
158+ * @return true if TLS is enabled
159+ */
160+ public boolean isEnableTls () {
161+ return enableTls ;
162+ }
163+
164+ /**
165+ * Returns the keystore file path.
166+ *
167+ * @return the keystore path, or null if TLS is disabled
168+ */
169+ public String getKeystorePath () {
170+ return keystorePath ;
171+ }
172+
173+ /**
174+ * Returns the keystore password.
175+ *
176+ * @return the keystore password, or null if TLS is disabled
177+ */
178+ public String getKeystorePassword () {
179+ return keystorePassword ;
180+ }
181+
182+ /**
183+ * Returns the key alias in the keystore.
184+ *
185+ * @return the key alias, or null if TLS is disabled
186+ */
187+ public String getKeyAlias () {
188+ return keyAlias ;
189+ }
190+
191+ /**
192+ * Returns the enabled TLS protocols.
193+ *
194+ * @return array of enabled TLS protocol names (e.g., "TLSv1.2", "TLSv1.3")
195+ */
196+ public String [] getEnabledProtocols () {
197+ return enabledProtocols != null ? enabledProtocols .clone () : null ;
198+ }
199+
200+ /**
201+ * Returns the enabled cipher suites.
202+ *
203+ * @return array of enabled cipher suite names
204+ */
205+ public String [] getEnabledCiphers () {
206+ return enabledCiphers != null ? enabledCiphers .clone () : null ;
207+ }
208+
143209 /**
144210 * Creates a new builder for constructing API server configurations.
145211 *
@@ -159,6 +225,17 @@ public static class Builder {
159225 private Set <String > allowedOrigins = new HashSet <>();
160226 private int threadPoolSize = 20 ;
161227 private int maxThreadPoolSize = 200 ;
228+ private boolean enableTls = false ;
229+ private String keystorePath ;
230+ private String keystorePassword ;
231+ private String keyAlias = "server" ;
232+ private String [] enabledProtocols = {"TLSv1.2" , "TLSv1.3" };
233+ private String [] enabledCiphers = {
234+ "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" ,
235+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" ,
236+ "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" ,
237+ "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
238+ };
162239
163240 /**
164241 * Sets the port to listen on.
@@ -297,12 +374,98 @@ public Builder maxThreadPoolSize(int size) {
297374 return this ;
298375 }
299376
377+ /**
378+ * Enables TLS/HTTPS for the API server.
379+ *
380+ * @param enableTls true to enable TLS
381+ * @return this builder
382+ */
383+ public Builder enableTls (boolean enableTls ) {
384+ this .enableTls = enableTls ;
385+ return this ;
386+ }
387+
388+ /**
389+ * Sets the keystore file path for TLS certificates.
390+ *
391+ * @param keystorePath path to the keystore file
392+ * @return this builder
393+ */
394+ public Builder keystorePath (String keystorePath ) {
395+ this .keystorePath = keystorePath ;
396+ return this ;
397+ }
398+
399+ /**
400+ * Sets the keystore password.
401+ *
402+ * @param keystorePassword the keystore password
403+ * @return this builder
404+ */
405+ public Builder keystorePassword (String keystorePassword ) {
406+ this .keystorePassword = keystorePassword ;
407+ return this ;
408+ }
409+
410+ /**
411+ * Sets the key alias in the keystore.
412+ *
413+ * @param keyAlias the alias of the key to use
414+ * @return this builder
415+ */
416+ public Builder keyAlias (String keyAlias ) {
417+ this .keyAlias = keyAlias ;
418+ return this ;
419+ }
420+
421+ /**
422+ * Sets the enabled TLS protocols. Only TLSv1.2+ should be used.
423+ *
424+ * @param protocols array of protocol names (e.g., "TLSv1.2", "TLSv1.3")
425+ * @return this builder
426+ */
427+ public Builder enabledProtocols (String [] protocols ) {
428+ this .enabledProtocols = protocols ;
429+ return this ;
430+ }
431+
432+ /**
433+ * Sets the enabled cipher suites. Should only include strong ciphers.
434+ *
435+ * @param ciphers array of cipher suite names
436+ * @return this builder
437+ */
438+ public Builder enabledCiphers (String [] ciphers ) {
439+ this .enabledCiphers = ciphers ;
440+ return this ;
441+ }
442+
300443 /**
301444 * Builds the ApiServerConfig instance.
302445 *
446+ * <p><strong>BREAKING CHANGE (Issue #366):</strong> As of version 2.0, TLS configuration is
447+ * now validated at build time. If you enable TLS ({@code enableTls(true)}), you MUST also
448+ * provide a keystore path and password. Invalid TLS configurations will throw
449+ * IllegalStateException instead of being silently accepted.
450+ *
451+ * <p><strong>Migration Guide:</strong>
452+ * - If TLS is not needed: explicitly call {@code enableTls(false)} (or rely on default)
453+ * - If enabling TLS: provide keystore path and password:
454+ * <pre>{@code
455+ * ApiServerConfig.builder()
456+ * .enableTls(true)
457+ * .keystorePath("/path/to/keystore.jks")
458+ * .keystorePassword(System.getenv("KEYSTORE_PASSWORD"))
459+ * .build()
460+ * }</pre>
461+ *
303462 * @return a new ApiServerConfig
304463 * @throws IllegalStateException if enableAuth is true but apiKey is not set
305464 * @throws IllegalStateException if maxThreadPoolSize is less than threadPoolSize
465+ * @throws IllegalStateException if enableTls is true but keystorePath or keystorePassword is
466+ * not set, or if keystorePath/keystorePassword is empty/whitespace
467+ * @throws IllegalStateException if enableTls is true but no TLS protocols are configured
468+ * @throws IllegalStateException if weak TLS protocols (TLSv1.0, TLSv1.1) are enabled
306469 */
307470 public ApiServerConfig build () {
308471 if (enableAuth && (apiKey == null || apiKey .trim ().isEmpty ())) {
@@ -331,6 +494,34 @@ public ApiServerConfig build() {
331494 }
332495 }
333496
497+ // Validate TLS configuration
498+ if (enableTls ) {
499+ if (keystorePath == null || keystorePath .trim ().isEmpty ()) {
500+ throw new IllegalStateException (
501+ "SECURITY: Keystore path is REQUIRED when TLS is enabled. "
502+ + "Set via: ApiServerConfig.builder().keystorePath(\" /path/to/keystore.jks\" )" );
503+ }
504+ if (keystorePassword == null || keystorePassword .trim ().isEmpty ()) {
505+ throw new IllegalStateException (
506+ "SECURITY: Keystore password is REQUIRED when TLS is enabled. "
507+ + "Set via: ApiServerConfig.builder().keystorePassword(...) "
508+ + "or environment variable" );
509+ }
510+ if (enabledProtocols == null || enabledProtocols .length == 0 ) {
511+ throw new IllegalStateException ("SECURITY: At least one TLS protocol must be enabled" );
512+ }
513+ // Validate no weak protocols are enabled
514+ for (String protocol : enabledProtocols ) {
515+ if (protocol .matches (".*TLSv1(\\ .0)?$" ) || protocol .matches (".*TLSv1\\ .1$" )) {
516+ throw new IllegalStateException (
517+ "SECURITY: Weak TLS protocol detected: "
518+ + protocol
519+ + ". Only TLSv1.2+ are allowed. "
520+ + "Use: ApiServerConfig.builder().enabledProtocols(\" TLSv1.2\" , \" TLSv1.3\" )" );
521+ }
522+ }
523+ }
524+
334525 if (maxThreadPoolSize < threadPoolSize ) {
335526 throw new IllegalStateException (
336527 "Max thread pool size ("
0 commit comments