@@ -39,8 +39,14 @@ public class ServiceStubsOptions {
3939
4040 protected final @ Nullable Consumer <ManagedChannelBuilder <?>> channelInitializer ;
4141
42- /** Indicates whether basic HTTPS/SSL/TLS should be enabled * */
43- protected final boolean enableHttps ;
42+ /**
43+ * Indicates whether basic HTTPS/SSL/TLS should be enabled. Null means not explicitly set by the
44+ * user, allowing runtime derivation of the effective value.
45+ */
46+ protected final Boolean enableHttps ;
47+
48+ /** Indicates whether an API key was provided, used for automatic TLS enablement */
49+ protected final boolean apiKeyProvided ;
4450
4551 /** The user provided context for SSL/TLS over gRPC * */
4652 protected final SslContext sslContext ;
@@ -113,6 +119,7 @@ public class ServiceStubsOptions {
113119 this .target = that .target ;
114120 this .channelInitializer = that .channelInitializer ;
115121 this .enableHttps = that .enableHttps ;
122+ this .apiKeyProvided = that .apiKeyProvided ;
116123 this .sslContext = that .sslContext ;
117124 this .healthCheckAttemptTimeout = that .healthCheckAttemptTimeout ;
118125 this .systemInfoTimeout = that .systemInfoTimeout ;
@@ -134,7 +141,8 @@ public class ServiceStubsOptions {
134141 ManagedChannel channel ,
135142 String target ,
136143 @ Nullable Consumer <ManagedChannelBuilder <?>> channelInitializer ,
137- boolean enableHttps ,
144+ Boolean enableHttps ,
145+ boolean apiKeyProvided ,
138146 SslContext sslContext ,
139147 Duration healthCheckAttemptTimeout ,
140148 Duration healthCheckTimeout ,
@@ -154,6 +162,7 @@ public class ServiceStubsOptions {
154162 this .target = target ;
155163 this .channelInitializer = channelInitializer ;
156164 this .enableHttps = enableHttps ;
165+ this .apiKeyProvided = apiKeyProvided ;
157166 this .sslContext = sslContext ;
158167 this .healthCheckAttemptTimeout = healthCheckAttemptTimeout ;
159168 this .healthCheckTimeout = healthCheckTimeout ;
@@ -202,11 +211,24 @@ public Consumer<ManagedChannelBuilder<?>> getChannelInitializer() {
202211 }
203212
204213 /**
205- * @return if gRPC should use SSL/TLS; Ignored and assumed {@code true} if {@link
206- * #getSslContext()} is set
214+ * Returns whether gRPC should use SSL/TLS. This method computes the effective value based on:
215+ *
216+ * <ul>
217+ * <li>If explicitly set via {@link Builder#setEnableHttps(boolean)}, returns that value
218+ * <li>If an API key was provided and no custom SSL context or channel is set, returns {@code
219+ * true} (auto-enabled)
220+ * <li>Otherwise returns {@code false}
221+ * </ul>
222+ *
223+ * <p>Note: When {@link #getSslContext()} is set, TLS is handled by the SSL context regardless of
224+ * this value.
225+ *
226+ * @return if gRPC should use SSL/TLS
207227 */
208228 public boolean getEnableHttps () {
209- return enableHttps ;
229+ return (enableHttps != null )
230+ ? enableHttps
231+ : (apiKeyProvided && sslContext == null && channel == null );
210232 }
211233
212234 /**
@@ -325,12 +347,13 @@ public boolean equals(Object o) {
325347 if (this == o ) return true ;
326348 if (o == null || getClass () != o .getClass ()) return false ;
327349 ServiceStubsOptions that = (ServiceStubsOptions ) o ;
328- return enableHttps == that .enableHttps
350+ return apiKeyProvided == that .apiKeyProvided
329351 && enableKeepAlive == that .enableKeepAlive
330352 && keepAlivePermitWithoutStream == that .keepAlivePermitWithoutStream
331353 && Objects .equals (channel , that .channel )
332354 && Objects .equals (target , that .target )
333355 && Objects .equals (channelInitializer , that .channelInitializer )
356+ && Objects .equals (enableHttps , that .enableHttps )
334357 && Objects .equals (sslContext , that .sslContext )
335358 && Objects .equals (healthCheckAttemptTimeout , that .healthCheckAttemptTimeout )
336359 && Objects .equals (healthCheckTimeout , that .healthCheckTimeout )
@@ -353,6 +376,7 @@ public int hashCode() {
353376 target ,
354377 channelInitializer ,
355378 enableHttps ,
379+ apiKeyProvided ,
356380 sslContext ,
357381 healthCheckAttemptTimeout ,
358382 healthCheckTimeout ,
@@ -418,7 +442,7 @@ public String toString() {
418442 public static class Builder <T extends Builder <T >> {
419443 private ManagedChannel channel ;
420444 private SslContext sslContext ;
421- private boolean enableHttps ;
445+ private Boolean enableHttps ;
422446 private String target ;
423447 private Consumer <ManagedChannelBuilder <?>> channelInitializer ;
424448 private Duration healthCheckAttemptTimeout ;
@@ -435,6 +459,7 @@ public static class Builder<T extends Builder<T>> {
435459 private Collection <GrpcMetadataProvider > grpcMetadataProviders ;
436460 private Collection <ClientInterceptor > grpcClientInterceptors ;
437461 private Scope metricsScope ;
462+ private boolean apiKeyProvided ;
438463
439464 protected Builder () {}
440465
@@ -443,6 +468,7 @@ protected Builder(ServiceStubsOptions options) {
443468 this .target = options .target ;
444469 this .channelInitializer = options .channelInitializer ;
445470 this .enableHttps = options .enableHttps ;
471+ this .apiKeyProvided = options .apiKeyProvided ;
446472 this .sslContext = options .sslContext ;
447473 this .healthCheckAttemptTimeout = options .healthCheckAttemptTimeout ;
448474 this .healthCheckTimeout = options .healthCheckTimeout ;
@@ -455,8 +481,15 @@ protected Builder(ServiceStubsOptions options) {
455481 this .connectionBackoffResetFrequency = options .connectionBackoffResetFrequency ;
456482 this .grpcReconnectFrequency = options .grpcReconnectFrequency ;
457483 this .headers = options .headers ;
458- this .grpcMetadataProviders = options .grpcMetadataProviders ;
459- this .grpcClientInterceptors = options .grpcClientInterceptors ;
484+ // Make mutable copies of collections to allow adding more items
485+ this .grpcMetadataProviders =
486+ options .grpcMetadataProviders != null && !options .grpcMetadataProviders .isEmpty ()
487+ ? new ArrayList <>(options .grpcMetadataProviders )
488+ : null ;
489+ this .grpcClientInterceptors =
490+ options .grpcClientInterceptors != null && !options .grpcClientInterceptors .isEmpty ()
491+ ? new ArrayList <>(options .grpcClientInterceptors )
492+ : null ;
460493 this .metricsScope = options .metricsScope ;
461494 }
462495
@@ -613,6 +646,7 @@ public T addGrpcMetadataProvider(GrpcMetadataProvider grpcMetadataProvider) {
613646 * @return {@code this}
614647 */
615648 public T addApiKey (AuthorizationTokenSupplier apiKey ) {
649+ this .apiKeyProvided = true ;
616650 addGrpcMetadataProvider (
617651 new AuthorizationGrpcMetadataProvider (() -> "Bearer " + apiKey .supply ()));
618652 return self ();
@@ -804,6 +838,7 @@ public ServiceStubsOptions build() {
804838 this .target ,
805839 this .channelInitializer ,
806840 this .enableHttps ,
841+ this .apiKeyProvided ,
807842 this .sslContext ,
808843 this .healthCheckAttemptTimeout ,
809844 this .healthCheckTimeout ,
@@ -837,7 +872,7 @@ public ServiceStubsOptions validateAndBuildWithDefaults() {
837872 "Only one of the 'sslContext' or 'channel' options can be set at a time" );
838873 }
839874
840- if (this .enableHttps && this .channel != null ) {
875+ if (Boolean . TRUE . equals ( this .enableHttps ) && this .channel != null ) {
841876 throw new IllegalStateException (
842877 "Only one of the 'enableHttps' or 'channel' options can be set at a time" );
843878 }
@@ -866,6 +901,7 @@ public ServiceStubsOptions validateAndBuildWithDefaults() {
866901 target ,
867902 this .channelInitializer ,
868903 this .enableHttps ,
904+ this .apiKeyProvided ,
869905 this .sslContext ,
870906 healthCheckAttemptTimeout ,
871907 healthCheckTimeout ,
0 commit comments