+ * Handshaker service accepts a stream of handshaker request, returning a + * stream of handshaker response. Client is expected to send exactly one + * message with either client_start or server_start followed by one or more + * messages with next. Each time client sends a request, the handshaker + * service expects to respond. Client does not have to wait for service's + * response before sending next request. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall
+ * Gets the backend distribution for RPCs sent by a test client. + *+ */ + public io.grpc.testing.integration.Messages.LoadBalancerStatsResponse getClientStats(io.grpc.testing.integration.Messages.LoadBalancerStatsRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetClientStatsMethod(), getCallOptions(), request); + } + + /** + *
+ * Gets the accumulated stats for RPCs sent by a test client. + *+ */ + public io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsResponse getClientAccumulatedStats(io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetClientAccumulatedStatsMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service LoadBalancerStatsService. + *
+ * A service used to obtain stats for verifying LB behavior. + *+ */ public static final class LoadBalancerStatsServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * Returns the values of all the gauges that are currently being maintained by + * the service + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall, io.grpc.testing.integration.Metrics.GaugeResponse> + getAllGauges(io.grpc.testing.integration.Metrics.EmptyMessage request) { + return io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall( + getChannel(), getGetAllGaugesMethod(), getCallOptions(), request); + } + + /** + *
+ * Returns the value of one gauge + *+ */ + public io.grpc.testing.integration.Metrics.GaugeResponse getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetGaugeMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service MetricsService. + */ public static final class MetricsServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * A service used to control reconnect server. + *+ */ public static final class ReconnectServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * One empty request followed by one empty response. + *+ */ + public io.grpc.testing.integration.EmptyProtos.Empty emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getEmptyCallMethod(), getCallOptions(), request); + } + + /** + *
+ * One request followed by one response. + *+ */ + public io.grpc.testing.integration.Messages.SimpleResponse unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getUnaryCallMethod(), getCallOptions(), request); + } + + /** + *
+ * One request followed by one response. Response has cache control + * headers set such that a caching HTTP proxy (such as GFE) can + * satisfy subsequent requests. + *+ */ + public io.grpc.testing.integration.Messages.SimpleResponse cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCacheableUnaryCallMethod(), getCallOptions(), request); + } + + /** + *
+ * One request followed by a sequence of responses (streamed download). + * The server returns the payload with client desired type and sizes. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall, io.grpc.testing.integration.Messages.StreamingOutputCallResponse> + streamingOutputCall(io.grpc.testing.integration.Messages.StreamingOutputCallRequest request) { + return io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall( + getChannel(), getStreamingOutputCallMethod(), getCallOptions(), request); + } + + /** + *
+ * A sequence of requests followed by one response (streamed upload). + * The server returns the aggregated size of client payload as the result. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall
+ * A sequence of requests with each request served by the server immediately. + * As one request could lead to multiple responses, this interface + * demonstrates the idea of full duplexing. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall
+ * A sequence of requests followed by a sequence of responses. + * The server buffers all the client requests and then serves them in order. A + * stream of responses are returned to the client when the server starts with + * first request. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall
+ * The test server will not implement this method. It will be used + * to test the behavior when clients call unimplemented methods. + *+ */ + public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getUnimplementedCallMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service TestService. + *
+ * A simple service to test the various types of RPCs and experiment with + * performance with various types of payload. + *+ */ public static final class TestServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * A call that no server should implement + *+ */ + public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getUnimplementedCallMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service UnimplementedService. + *
+ * A simple service NOT implemented at servers so clients can test for + * that case. + *+ */ public static final class UnimplementedServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * Update the tes client's configuration. + *+ */ + public io.grpc.testing.integration.Messages.ClientConfigureResponse configure(io.grpc.testing.integration.Messages.ClientConfigureRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getConfigureMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service XdsUpdateClientConfigureService. + *
+ * A service to dynamically update the configuration of an xDS test client. + *+ */ public static final class XdsUpdateClientConfigureServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * A service to remotely control health status of an xDS test server. + *+ */ public static final class XdsUpdateHealthServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * Gets the backend distribution for RPCs sent by a test client. + *+ */ + public io.grpc.testing.integration.Messages.LoadBalancerStatsResponse getClientStats(io.grpc.testing.integration.Messages.LoadBalancerStatsRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetClientStatsMethod(), getCallOptions(), request); + } + + /** + *
+ * Gets the accumulated stats for RPCs sent by a test client. + *+ */ + public io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsResponse getClientAccumulatedStats(io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetClientAccumulatedStatsMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service LoadBalancerStatsService. + *
+ * A service used to obtain stats for verifying LB behavior. + *+ */ public static final class LoadBalancerStatsServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * Returns the values of all the gauges that are currently being maintained by + * the service + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall, io.grpc.testing.integration.Metrics.GaugeResponse> + getAllGauges(io.grpc.testing.integration.Metrics.EmptyMessage request) { + return io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall( + getChannel(), getGetAllGaugesMethod(), getCallOptions(), request); + } + + /** + *
+ * Returns the value of one gauge + *+ */ + public io.grpc.testing.integration.Metrics.GaugeResponse getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetGaugeMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service MetricsService. + */ public static final class MetricsServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * A service used to control reconnect server. + *+ */ public static final class ReconnectServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * One empty request followed by one empty response. + *+ */ + public io.grpc.testing.integration.EmptyProtos.Empty emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getEmptyCallMethod(), getCallOptions(), request); + } + + /** + *
+ * One request followed by one response. + *+ */ + public io.grpc.testing.integration.Messages.SimpleResponse unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getUnaryCallMethod(), getCallOptions(), request); + } + + /** + *
+ * One request followed by one response. Response has cache control + * headers set such that a caching HTTP proxy (such as GFE) can + * satisfy subsequent requests. + *+ */ + public io.grpc.testing.integration.Messages.SimpleResponse cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCacheableUnaryCallMethod(), getCallOptions(), request); + } + + /** + *
+ * One request followed by a sequence of responses (streamed download). + * The server returns the payload with client desired type and sizes. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall, io.grpc.testing.integration.Messages.StreamingOutputCallResponse> + streamingOutputCall(io.grpc.testing.integration.Messages.StreamingOutputCallRequest request) { + return io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall( + getChannel(), getStreamingOutputCallMethod(), getCallOptions(), request); + } + + /** + *
+ * A sequence of requests followed by one response (streamed upload). + * The server returns the aggregated size of client payload as the result. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall
+ * A sequence of requests with each request served by the server immediately. + * As one request could lead to multiple responses, this interface + * demonstrates the idea of full duplexing. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall
+ * A sequence of requests followed by a sequence of responses. + * The server buffers all the client requests and then serves them in order. A + * stream of responses are returned to the client when the server starts with + * first request. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall
+ * The test server will not implement this method. It will be used + * to test the behavior when clients call unimplemented methods. + *+ */ + public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getUnimplementedCallMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service TestService. + *
+ * A simple service to test the various types of RPCs and experiment with + * performance with various types of payload. + *+ */ public static final class TestServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * A call that no server should implement + *+ */ + public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getUnimplementedCallMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service UnimplementedService. + *
+ * A simple service NOT implemented at servers so clients can test for + * that case. + *+ */ public static final class UnimplementedServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * Update the tes client's configuration. + *+ */ + public io.grpc.testing.integration.Messages.ClientConfigureResponse configure(io.grpc.testing.integration.Messages.ClientConfigureRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getConfigureMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service XdsUpdateClientConfigureService. + *
+ * A service to dynamically update the configuration of an xDS test client. + *+ */ public static final class XdsUpdateClientConfigureServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * A service to remotely control health status of an xDS test server. + *+ */ public static final class XdsUpdateHealthServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
This API is experimental and
+ * subject to change.
+ *
+ * @param supplier {@link Supplier} to use to produce the value.
+ * @see io.grpc.ExperimentalApi
+ * @return result of supplier.
+ */
+ public It is called for each individual RPC, within the {@link Context} of the call, before the
* stream is about to be created on a transport. Implementations should not block in this
* method. If metadata is not immediately available, e.g., needs to be fetched from network, the
- * implementation may give the {@code applier} to an asynchronous task which will eventually call
+ * implementation may give the {@code appExecutor} an asynchronous task which will eventually call
* the {@code applier}. The RPC proceeds only after the {@code applier} is called.
*
* @param requestInfo request-related information
diff --git a/api/src/main/java/io/grpc/CallOptions.java b/api/src/main/java/io/grpc/CallOptions.java
index 25c4df386a1..800bdfb6c90 100644
--- a/api/src/main/java/io/grpc/CallOptions.java
+++ b/api/src/main/java/io/grpc/CallOptions.java
@@ -17,16 +17,18 @@
package io.grpc;
import static com.google.common.base.Preconditions.checkArgument;
+import static io.grpc.TimeUtils.convertToNanos;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
+import com.google.errorprone.annotations.CheckReturnValue;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
-import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@@ -176,6 +178,11 @@ public CallOptions withDeadlineAfter(long duration, TimeUnit unit) {
return withDeadline(Deadline.after(duration, unit));
}
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11657")
+ public CallOptions withDeadlineAfter(Duration duration) {
+ return withDeadlineAfter(convertToNanos(duration), TimeUnit.NANOSECONDS);
+ }
+
/**
* Returns the deadline or {@code null} if the deadline is not set.
*/
diff --git a/api/src/main/java/io/grpc/Channel.java b/api/src/main/java/io/grpc/Channel.java
index 60ff76ff082..e2787eb2f26 100644
--- a/api/src/main/java/io/grpc/Channel.java
+++ b/api/src/main/java/io/grpc/Channel.java
@@ -16,7 +16,6 @@
package io.grpc;
-import javax.annotation.concurrent.ThreadSafe;
/**
* A virtual connection to a conceptual endpoint, to perform RPCs. A channel is free to have zero or
@@ -29,8 +28,9 @@
* implementations using {@link ClientInterceptor}. It is expected that most application
* code will not use this class directly but rather work with stubs that have been bound to a
* Channel that was decorated during application initialization.
+ *
+ * This class is thread-safe.
*/
-@ThreadSafe
public abstract class Channel {
/**
* Create a {@link ClientCall} to the remote operation specified by the given
diff --git a/api/src/main/java/io/grpc/ChannelConfigurator.java b/api/src/main/java/io/grpc/ChannelConfigurator.java
new file mode 100644
index 00000000000..35f521ac0d9
--- /dev/null
+++ b/api/src/main/java/io/grpc/ChannelConfigurator.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2026 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc;
+
+
+
+/**
+ * A configurator for child channels created by gRPC's internal infrastructure.
+ *
+ * This interface allows users to inject configuration (such as credentials, interceptors,
+ * or flow control settings) into channels created automatically by gRPC for control plane
+ * operations. Common use cases include:
+ * Usage Example:
+ * Implementations must be thread-safe as the configure methods may be invoked concurrently
+ * by multiple internal components.
+ *
+ * @since 1.83.0
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/12574")
+@FunctionalInterface
+public interface ChannelConfigurator {
+
+ /**
+ * Configures a builder for a new child channel.
+ *
+ * This method is invoked synchronously during the creation of the child channel,
+ * before {@link ManagedChannelBuilder#build()} is called.
+ *
+ * Note: Implementations must only apply configurations to the
+ * provided builder and must NOT call {@code builder.build()} themselves.
+ *
+ * Note: The provided {@code builder} is generic ({@code ?}). Implementations
+ * should use universal configuration methods (like {@code intercept()}, {@code userAgent()})
+ * on the builder rather than casting it to specific implementation types.
+ *
+ * @param builder the mutable channel builder for the new child channel
+ */
+ void configureChannelBuilder(ManagedChannelBuilder> builder);
+}
diff --git a/api/src/main/java/io/grpc/ChannelLogger.java b/api/src/main/java/io/grpc/ChannelLogger.java
index ce654ec9d5b..2cdf4c84724 100644
--- a/api/src/main/java/io/grpc/ChannelLogger.java
+++ b/api/src/main/java/io/grpc/ChannelLogger.java
@@ -16,15 +16,15 @@
package io.grpc;
-import javax.annotation.concurrent.ThreadSafe;
/**
* A Channel-specific logger provided by GRPC library to {@link LoadBalancer} implementations.
* Information logged here goes to Channelz, and to the Java logger of this class
* as well.
+ *
+ * This class is thread-safe.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/5029")
-@ThreadSafe
public abstract class ChannelLogger {
/**
* Log levels. See the table below for the mapping from the ChannelLogger levels to Channelz
diff --git a/api/src/main/java/io/grpc/ClientCall.java b/api/src/main/java/io/grpc/ClientCall.java
index df9e15001e1..c915c8beaac 100644
--- a/api/src/main/java/io/grpc/ClientCall.java
+++ b/api/src/main/java/io/grpc/ClientCall.java
@@ -67,7 +67,7 @@
* manner, and notifies gRPC library to receive additional response after one is consumed by
* a fictional From gRPC's perspective, interceptors don't generally exist and are more of a convenience.
+ * Convenience APIs will use {@link ClientInterceptors} to convert the interceptor into a {@code
+ * Channel}. Thus interceptors are an extension of the application and run on the same
+ * threads and receive callbacks using the same executor.
+ *
* The interceptor may be called for multiple {@link ClientCall calls} by one or more threads
* without completing the previous ones first. Refer to the
* {@link io.grpc.ClientCall.Listener ClientCall.Listener} docs for more details regarding thread
* safety of the returned listener.
+ *
+ * This is thread-safe and should be considered
+ * for the errorprone ThreadSafe annotation in the future.
*/
-@ThreadSafe
public interface ClientInterceptor {
/**
* Intercept {@link ClientCall} creation by the {@code next} {@link Channel}.
diff --git a/api/src/main/java/io/grpc/ClientStreamTracer.java b/api/src/main/java/io/grpc/ClientStreamTracer.java
index 2f366b7404c..d654fd33358 100644
--- a/api/src/main/java/io/grpc/ClientStreamTracer.java
+++ b/api/src/main/java/io/grpc/ClientStreamTracer.java
@@ -19,13 +19,13 @@
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.MoreObjects;
-import javax.annotation.concurrent.ThreadSafe;
/**
* {@link StreamTracer} for the client-side.
+ *
+ * This class is thread-safe.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2861")
-@ThreadSafe
public abstract class ClientStreamTracer extends StreamTracer {
/**
* Indicates how long the call was delayed, in nanoseconds, due to waiting for name resolution
@@ -57,6 +57,45 @@ public void streamCreated(@Grpc.TransportAttr Attributes transportAttrs, Metadat
public void createPendingStream() {
}
+ /**
+ * Called when an attempt-level delay segment (such as waiting for a load balancing pick or
+ * connection establishment) starts.
+ *
+ * This method is invoked synchronously on the attempt thread. Implementations should start
+ * internal timers or child tracing spans (named strictly {@code "Attempt Delay"}) carrying the
+ * canonical {@code grpc.delay_type} attribute.
+ *
+ * @param delayType canonical low-cardinality label categorizing the delay (e.g., "connecting")
+ * @param delayReason high-cardinality diagnostic string describing granular runtime conditions
+ * @since 1.82.0
+ */
+ public void recordAttemptDelayStart(String delayType, String delayReason) {
+ }
+
+ /**
+ * Called when an attempt-level delay reason changes while the overall delay type remains
+ * constant (for example, when a priority load balancing policy fails over between tiers).
+ *
+ * Implementations should record structured events (such as {@code "Delay state transition"})
+ * on the active delay span without recreating the span or resetting cumulative timers.
+ *
+ * @param delayReason updated high-cardinality diagnostic string describing new conditions
+ * @since 1.82.0
+ */
+ public void recordAttemptDelayReasonChanged(String delayReason) {
+ }
+
+ /**
+ * Called when an attempt-level delay segment ends upon successful pick or stream creation.
+ *
+ * Implementations should simultaneously close active child tracing spans and record elapsed
+ * duration to the {@code grpc.client.attempt.delay.duration} histogram.
+ *
+ * @since 1.82.0
+ */
+ public void recordAttemptDelayEnd() {
+ }
+
/**
* Headers has been sent to the socket.
*/
@@ -132,12 +171,15 @@ public static final class StreamInfo {
private final CallOptions callOptions;
private final int previousAttempts;
private final boolean isTransparentRetry;
+ private final boolean isHedging;
StreamInfo(
- CallOptions callOptions, int previousAttempts, boolean isTransparentRetry) {
+ CallOptions callOptions, int previousAttempts, boolean isTransparentRetry,
+ boolean isHedging) {
this.callOptions = checkNotNull(callOptions, "callOptions");
this.previousAttempts = previousAttempts;
this.isTransparentRetry = isTransparentRetry;
+ this.isHedging = isHedging;
}
/**
@@ -165,6 +207,15 @@ public boolean isTransparentRetry() {
return isTransparentRetry;
}
+ /**
+ * Whether the stream is hedging.
+ *
+ * @since 1.74.0
+ */
+ public boolean isHedging() {
+ return isHedging;
+ }
+
/**
* Converts this StreamInfo into a new Builder.
*
@@ -174,7 +225,9 @@ public Builder toBuilder() {
return new Builder()
.setCallOptions(callOptions)
.setPreviousAttempts(previousAttempts)
- .setIsTransparentRetry(isTransparentRetry);
+ .setIsTransparentRetry(isTransparentRetry)
+ .setIsHedging(isHedging);
+
}
/**
@@ -192,6 +245,7 @@ public String toString() {
.add("callOptions", callOptions)
.add("previousAttempts", previousAttempts)
.add("isTransparentRetry", isTransparentRetry)
+ .add("isHedging", isHedging)
.toString();
}
@@ -204,6 +258,7 @@ public static final class Builder {
private CallOptions callOptions = CallOptions.DEFAULT;
private int previousAttempts;
private boolean isTransparentRetry;
+ private boolean isHedging;
Builder() {
}
@@ -236,11 +291,21 @@ public Builder setIsTransparentRetry(boolean isTransparentRetry) {
return this;
}
+ /**
+ * Sets whether the stream is hedging.
+ *
+ * @since 1.74.0
+ */
+ public Builder setIsHedging(boolean isHedging) {
+ this.isHedging = isHedging;
+ return this;
+ }
+
/**
* Builds a new StreamInfo.
*/
public StreamInfo build() {
- return new StreamInfo(callOptions, previousAttempts, isTransparentRetry);
+ return new StreamInfo(callOptions, previousAttempts, isTransparentRetry, isHedging);
}
}
}
diff --git a/api/src/main/java/io/grpc/ConfiguratorRegistry.java b/api/src/main/java/io/grpc/ConfiguratorRegistry.java
index b2efcc1cff4..19d6703d308 100644
--- a/api/src/main/java/io/grpc/ConfiguratorRegistry.java
+++ b/api/src/main/java/io/grpc/ConfiguratorRegistry.java
@@ -16,10 +16,10 @@
package io.grpc;
+import com.google.errorprone.annotations.concurrent.GuardedBy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import javax.annotation.concurrent.GuardedBy;
/**
* A registry for {@link Configurator} instances.
@@ -33,9 +33,9 @@ final class ConfiguratorRegistry {
@GuardedBy("this")
private boolean wasConfiguratorsSet;
@GuardedBy("this")
- private boolean configFrozen;
- @GuardedBy("this")
private List A {@code NameResolver}-compliant URI is an absolute hierarchical URI as defined by {@link
- * java.net.URI}. Example URIs:
+ * Example URIs:
* An authority string will be converted to a {@code NameResolver}-compliant URI, which has
- * the scheme from the name resolver with the highest priority (e.g. {@code "dns"}),
- * no authority, and the original authority string as its path after properly escaped.
- * We recommend libraries to specify the schema explicitly if it is known, since libraries cannot
- * know which NameResolver will be default during runtime.
+ * An authority string will be converted to a URI having the scheme of the name resolver with
+ * the highest priority (e.g. {@code "dns"}), the empty string as the authority, and
+ * {@code target} as its absolute path. We recommend libraries specify {@code target} as a URI
+ * instead since they cannot know which NameResolver will be default at runtime.
* Example authority strings:
* The URI form of {@code target} is preferred because it is less ambiguous. For example, the
+ * target string {@code foo:8080} is a valid authority string with host {@code foo} and port
+ * {@code 8080} but it is also a valid RFC 3986 URI with scheme {@code foo} and path {@code 8080}.
+ * gRPC prioritizes the URI form, which means {@code foo:8080} will be treated as a URI with
+ * scheme {@code foo}. Using {@code dns:///foo:8080} avoids this ambiguity.
*/
public static ManagedChannelBuilder> newChannelBuilder(
String target, ChannelCredentials creds) {
return ManagedChannelRegistry.getDefaultRegistry().newChannelBuilder(target, creds);
}
+ /**
+ * Creates a channel builder with a target string, credentials, and a specific
+ * name resolver registry.
+ *
+ * The provided {@code nameResolverRegistry} is used to resolve the target address
+ * into physical addresses (e.g., DNS or custom schemes).
+ *
+ * @param target the target URI for the channel, such as {@code "localhost:8080"}
+ * or {@code "dns:///example.com"}
+ * @param creds the channel credentials to use for secure communication
+ * @param nameResolverRegistry the registry used to look up {@link NameResolver}
+ * providers for the target
+ * @return a {@link ManagedChannelBuilder} instance configured with the given parameters
+ * @throws IllegalArgumentException if no provider is available for the given target
+ * or credentials
+ * @since 1.83.0
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/12694")
+ public static ManagedChannelBuilder> newChannelBuilder(
+ String target,
+ ChannelCredentials creds,
+ NameResolverRegistry nameResolverRegistry) {
+ return ManagedChannelRegistry.getDefaultRegistry().newChannelBuilder(
+ nameResolverRegistry,
+ target,
+ creds);
+ }
+
/**
* Creates a channel builder from a host, port, and credentials. The host and port are combined to
* form an authority string and then passed to {@link #newChannelBuilder(String,
diff --git a/api/src/main/java/io/grpc/HandlerRegistry.java b/api/src/main/java/io/grpc/HandlerRegistry.java
index 4aaf0114fb1..148573ada9a 100644
--- a/api/src/main/java/io/grpc/HandlerRegistry.java
+++ b/api/src/main/java/io/grpc/HandlerRegistry.java
@@ -19,12 +19,12 @@
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
-import javax.annotation.concurrent.ThreadSafe;
/**
* Registry of services and their methods used by servers to dispatching incoming calls.
+ *
+ * This class is thread-safe.
*/
-@ThreadSafe
public abstract class HandlerRegistry {
/**
diff --git a/api/src/main/java/io/grpc/HttpConnectProxiedSocketAddress.java b/api/src/main/java/io/grpc/HttpConnectProxiedSocketAddress.java
index d59c53db1d1..0df8dc452c1 100644
--- a/api/src/main/java/io/grpc/HttpConnectProxiedSocketAddress.java
+++ b/api/src/main/java/io/grpc/HttpConnectProxiedSocketAddress.java
@@ -23,6 +23,9 @@
import com.google.common.base.Objects;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
import javax.annotation.Nullable;
/**
@@ -33,6 +36,8 @@ public final class HttpConnectProxiedSocketAddress extends ProxiedSocketAddress
private final SocketAddress proxyAddress;
private final InetSocketAddress targetAddress;
+ @SuppressWarnings("serial")
+ private final Map FIELD is purposefully not supported, as Android wouldn't be able to ignore a field. Instead,
+ * the entire class would need to be avoided on Android.
+ */
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE})
+@interface IgnoreJRERequirement {}
diff --git a/api/src/main/java/io/grpc/InternalConfigSelector.java b/api/src/main/java/io/grpc/InternalConfigSelector.java
index 38856f440b4..a63009361d4 100644
--- a/api/src/main/java/io/grpc/InternalConfigSelector.java
+++ b/api/src/main/java/io/grpc/InternalConfigSelector.java
@@ -35,7 +35,7 @@ public abstract class InternalConfigSelector {
= Attributes.Key.create("internal:io.grpc.config-selector");
// Use PickSubchannelArgs for SelectConfigArgs for now. May change over time.
- /** Selects the config for an PRC. */
+ /** Selects the config for an RPC. */
public abstract Result selectConfig(LoadBalancer.PickSubchannelArgs args);
public static final class Result {
diff --git a/api/src/main/java/io/grpc/InternalConfiguratorRegistry.java b/api/src/main/java/io/grpc/InternalConfiguratorRegistry.java
index b495800ff13..f567dab74c4 100644
--- a/api/src/main/java/io/grpc/InternalConfiguratorRegistry.java
+++ b/api/src/main/java/io/grpc/InternalConfiguratorRegistry.java
@@ -48,4 +48,8 @@ public static void configureServerBuilder(ServerBuilder> serverBuilder) {
public static boolean wasSetConfiguratorsCalled() {
return ConfiguratorRegistry.getDefaultRegistry().wasSetConfiguratorsCalled();
}
+
+ public static int getConfiguratorsCallCountBeforeSet() {
+ return ConfiguratorRegistry.getDefaultRegistry().getConfiguratorsCallCountBeforeSet();
+ }
}
diff --git a/api/src/main/java/io/grpc/InternalEquivalentAddressGroup.java b/api/src/main/java/io/grpc/InternalEquivalentAddressGroup.java
new file mode 100644
index 00000000000..cd171208af7
--- /dev/null
+++ b/api/src/main/java/io/grpc/InternalEquivalentAddressGroup.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2026 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc;
+
+@Internal
+public final class InternalEquivalentAddressGroup {
+ private InternalEquivalentAddressGroup() {}
+
+ /**
+ * Endpoint weight for load balancing purposes. While the type is Long, it must be a valid uint32.
+ * Must not be zero. The weight is proportional to the other endpoints; if an endpoint's weight is
+ * twice that of another endpoint, it is intended to receive twice the load.
+ */
+ public static final Attributes.Key However, the actual running thread may be the network thread, thus the following rules must be
* followed to prevent blocking or even dead-locking in a network:
*
@@ -121,6 +120,12 @@ public abstract class LoadBalancer {
HEALTH_CONSUMER_LISTENER_ARG_KEY =
LoadBalancer.CreateSubchannelArgs.Key.create("internal:health-check-consumer-listener");
+ @Internal
+ public static final LoadBalancer.CreateSubchannelArgs.Key Implementations should not modify the given {@code servers}.
+ * Handles newly resolved addresses and metadata attributes from name resolution system.
+ * Addresses in {@link EquivalentAddressGroup} should be considered equivalent but may be
+ * flattened into a single list if needed.
*
* @param resolvedAddresses the resolved server addresses, attributes, and config.
* @since 1.21.0
+ *
+ * @deprecated Use instead {@link #acceptResolvedAddresses(ResolvedAddresses)}
*/
+ @Deprecated
public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
if (recursionCount++ == 0) {
// Note that the information about the addresses actually being accepted will be lost
@@ -173,12 +179,10 @@ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
* EquivalentAddressGroup} addresses should be considered equivalent but may be flattened into a
* single list if needed.
*
- * Implementations can choose to reject the given addresses by returning {@code false}.
- *
- * Implementations should not modify the given {@code addresses}.
+ * @param resolvedAddresses the resolved server addresses, attributes, and config
+ * @return {@code Status.OK} if the resolved addresses were accepted, otherwise an error to report
+ * to the name resolver
*
- * @param resolvedAddresses the resolved server addresses, attributes, and config.
- * @return {@code true} if the resolved addresses were accepted. {@code false} if rejected.
* @since 1.49.0
*/
public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
@@ -206,7 +210,7 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
*
* @since 1.21.0
*/
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11657")
public static final class ResolvedAddresses {
private final List This method should always return a constant value. It's not specified when this will be
* called.
+ *
+ * Note that this method is only called when implementing {@code handleResolvedAddresses()}
+ * instead of {@code acceptResolvedAddresses()}.
+ *
+ * @deprecated Instead of overwriting this and {@code handleResolvedAddresses()}, only
+ * overwrite {@code acceptResolvedAddresses()} which indicates if the addresses provided
+ * by the name resolver are acceptable with the {@code boolean} return value.
*/
+ @Deprecated
+ @SuppressWarnings("InlineMeSuggester")
public boolean canHandleEmptyAddressListFromNameResolution() {
return false;
}
@@ -436,7 +449,6 @@ public void requestConnection() {}
*
* @since 1.2.0
*/
- @ThreadSafe
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
public abstract static class SubchannelPicker {
/**
@@ -446,18 +458,6 @@ public abstract static class SubchannelPicker {
* @since 1.3.0
*/
public abstract PickResult pickSubchannel(PickSubchannelArgs args);
-
- /**
- * Tries to establish connections now so that the upcoming RPC may then just pick a ready
- * connection without having to connect first.
- *
- * No-op if unsupported.
- *
- * @deprecated override {@link LoadBalancer#requestConnection} instead.
- * @since 1.11.0
- */
- @Deprecated
- public void requestConnection() {}
}
/**
@@ -546,14 +546,33 @@ public static final class PickResult {
private final Status status;
// True if the result is created by withDrop()
private final boolean drop;
+ @Nullable private final String authorityOverride;
+ @Nullable private final String delayType;
+ @Nullable private final String delayReason;
private PickResult(
@Nullable Subchannel subchannel, @Nullable ClientStreamTracer.Factory streamTracerFactory,
Status status, boolean drop) {
+ this(subchannel, streamTracerFactory, status, drop, null, null, null);
+ }
+
+ private PickResult(
+ @Nullable Subchannel subchannel, @Nullable ClientStreamTracer.Factory streamTracerFactory,
+ Status status, boolean drop, @Nullable String authorityOverride) {
+ this(subchannel, streamTracerFactory, status, drop, authorityOverride, null, null);
+ }
+
+ private PickResult(
+ @Nullable Subchannel subchannel, @Nullable ClientStreamTracer.Factory streamTracerFactory,
+ Status status, boolean drop, @Nullable String authorityOverride,
+ @Nullable String delayType, @Nullable String delayReason) {
this.subchannel = subchannel;
this.streamTracerFactory = streamTracerFactory;
this.status = checkNotNull(status, "status");
this.drop = drop;
+ this.authorityOverride = authorityOverride;
+ this.delayType = delayType;
+ this.delayReason = delayReason;
}
/**
@@ -626,6 +645,8 @@ private PickResult(
* stream is created at all in some cases.
* @since 1.3.0
*/
+ // TODO(shivaspeaks): Need to deprecate old APIs and create new ones,
+ // per https://github.com/grpc/grpc-java/issues/12662.
public static PickResult withSubchannel(
Subchannel subchannel, @Nullable ClientStreamTracer.Factory streamTracerFactory) {
return new PickResult(
@@ -633,6 +654,19 @@ public static PickResult withSubchannel(
false);
}
+ /**
+ * Same as {@code withSubchannel(subchannel, streamTracerFactory)} but with an authority name
+ * to override in the host header.
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11656")
+ public static PickResult withSubchannel(
+ Subchannel subchannel, @Nullable ClientStreamTracer.Factory streamTracerFactory,
+ @Nullable String authorityOverride) {
+ return new PickResult(
+ checkNotNull(subchannel, "subchannel"), streamTracerFactory, Status.OK,
+ false, authorityOverride);
+ }
+
/**
* Equivalent to {@code withSubchannel(subchannel, null)}.
*
@@ -642,6 +676,30 @@ public static PickResult withSubchannel(Subchannel subchannel) {
return withSubchannel(subchannel, null);
}
+ /**
+ * Creates a new {@code PickResult} with the given {@code subchannel},
+ * but retains all other properties from this {@code PickResult}.
+ *
+ * @since 1.80.0
+ */
+ public PickResult copyWithSubchannel(Subchannel subchannel) {
+ return new PickResult(checkNotNull(subchannel, "subchannel"), streamTracerFactory,
+ status, drop, authorityOverride, delayType, delayReason);
+ }
+
+ /**
+ * Creates a new {@code PickResult} with the given {@code streamTracerFactory},
+ * but retains all other properties from this {@code PickResult}.
+ *
+ * @since 1.80.0
+ */
+ public PickResult copyWithStreamTracerFactory(
+ @Nullable ClientStreamTracer.Factory streamTracerFactory) {
+ return new PickResult(
+ subchannel, streamTracerFactory, status, drop, authorityOverride, delayType,
+ delayReason);
+ }
+
/**
* A decision to report a connectivity error to the RPC. If the RPC is {@link
* CallOptions#withWaitForReady wait-for-ready}, it will stay buffered. Otherwise, it will fail
@@ -676,6 +734,38 @@ public static PickResult withNoResult() {
return NO_RESULT;
}
+ /**
+ * No decision could be made. The RPC will stay buffered with a specific delay type and reason.
+ *
+ * @param delayType low-cardinality root cause label (e.g., "connecting")
+ * @param delayReason high-cardinality diagnostic string for trace events
+ * @since 1.82.0
+ */
+ public static PickResult withNoResult(String delayType, String delayReason) {
+ Preconditions.checkNotNull(delayType, "delayType");
+ Preconditions.checkNotNull(delayReason, "delayReason");
+ return new PickResult(null, null, Status.OK, false, null, delayType, delayReason);
+ }
+
+ /** Returns the delay type label if any. */
+ @Nullable
+ public String getDelayType() {
+ return delayType;
+ }
+
+ /** Returns the diagnostic delay reason if any. */
+ @Nullable
+ public String getDelayReason() {
+ return delayReason;
+ }
+
+ /** Returns the authority override if any. */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11656")
+ @Nullable
+ public String getAuthorityOverride() {
+ return authorityOverride;
+ }
+
/**
* The Subchannel if this result was created by {@link #withSubchannel withSubchannel()}, or
* null otherwise.
@@ -730,6 +820,7 @@ public String toString() {
.add("streamTracerFactory", streamTracerFactory)
.add("status", status)
.add("drop", drop)
+ .add("authority-override", authorityOverride)
.toString();
}
@@ -828,9 +919,11 @@ public String toString() {
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
public static final class Builder {
+ private static final Object[][] EMPTY_CUSTOM_OPTIONS = new Object[0][2];
+
private List This class is thread-safe.
+ *
* @since 1.2.0
*/
- @ThreadSafe
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
public abstract static class Helper {
/**
@@ -994,8 +1088,8 @@ public Subchannel createSubchannel(CreateSubchannelArgs args) {
}
/**
- * Out-of-band channel for LoadBalancer’s own RPC needs, e.g., talking to an external
- * load-balancer service.
+ * Create an out-of-band channel for the LoadBalancer’s own RPC needs, e.g., talking to an
+ * external load-balancer service.
*
* The LoadBalancer is responsible for closing unused OOB channels, and closing all OOB
* channels within {@link #shutdown}.
@@ -1005,7 +1099,12 @@ public Subchannel createSubchannel(CreateSubchannelArgs args) {
public abstract ManagedChannel createOobChannel(EquivalentAddressGroup eag, String authority);
/**
- * Accept a list of EAG for multiple authorities: https://github.com/grpc/grpc-java/issues/4618
+ * Create an out-of-band channel for the LoadBalancer's own RPC needs, e.g., talking to an
+ * external load-balancer service. This version of the method allows multiple EAGs, so different
+ * addresses can have different authorities.
+ *
+ * The LoadBalancer is responsible for closing unused OOB channels, and closing all OOB
+ * channels within {@link #shutdown}.
* */
public ManagedChannel createOobChannel(List Work added to the synchronization context might not run immediately, so LB implementations
+ * must be careful to ensure that any assumptions still hold when it is executed. In particular,
+ * the LB might have been shut down or subchannels might have changed state.
+ *
* Pro-tip: in order to call {@link SynchronizationContext#schedule}, you need to provide a
* {@link ScheduledExecutorService}. {@link #getScheduledExecutorService} is provided for your
* convenience.
@@ -1262,7 +1365,7 @@ public MetricRecorder getMetricRecorder() {
}
/**
- * A logical connection to a server, or a group of equivalent servers represented by an {@link
+ * A logical connection to a server, or a group of equivalent servers represented by an {@link
* EquivalentAddressGroup}.
*
* It maintains at most one physical connection (aka transport) for sending new RPCs, while
@@ -1481,9 +1584,10 @@ public interface SubchannelStateListener {
/**
* Factory to create {@link LoadBalancer} instance.
*
+ * This class is thread-safe.
+ *
* @since 1.2.0
*/
- @ThreadSafe
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
public abstract static class Factory {
/**
diff --git a/api/src/main/java/io/grpc/LoadBalancerProvider.java b/api/src/main/java/io/grpc/LoadBalancerProvider.java
index bb4c574211e..7dc30d6baaf 100644
--- a/api/src/main/java/io/grpc/LoadBalancerProvider.java
+++ b/api/src/main/java/io/grpc/LoadBalancerProvider.java
@@ -81,7 +81,7 @@ public abstract class LoadBalancerProvider extends LoadBalancer.Factory {
* @return a tuple of the fully parsed and validated balancer configuration, else the Status.
* @since 1.20.0
* @see
- * A24-lb-policy-config.md
+ * A24-lb-policy-config.md
*/
public ConfigOrError parseLoadBalancingPolicyConfig(Map This class is thread-safe.
*/
-@ThreadSafe
public abstract class ManagedChannel extends Channel {
/**
* Initiates an orderly shutdown in which preexisting calls continue but new calls are immediately
diff --git a/api/src/main/java/io/grpc/ManagedChannelBuilder.java b/api/src/main/java/io/grpc/ManagedChannelBuilder.java
index 6e30d8eae04..d7cc623c119 100644
--- a/api/src/main/java/io/grpc/ManagedChannelBuilder.java
+++ b/api/src/main/java/io/grpc/ManagedChannelBuilder.java
@@ -45,11 +45,10 @@ public static ManagedChannelBuilder> forAddress(String name, int port) {
}
/**
- * Creates a channel with a target string, which can be either a valid {@link
- * NameResolver}-compliant URI, or an authority string.
+ * Creates a channel with a target string, which can be either an RFC 3986 URI, or an authority
+ * string.
*
- * A {@code NameResolver}-compliant URI is an absolute hierarchical URI as defined by {@link
- * java.net.URI}. Example URIs:
+ * Example URIs:
* An authority string will be converted to a {@code NameResolver}-compliant URI, which has
- * the scheme from the name resolver with the highest priority (e.g. {@code "dns"}),
- * no authority, and the original authority string as its path after properly escaped.
- * We recommend libraries to specify the schema explicitly if it is known, since libraries cannot
- * know which NameResolver will be default during runtime.
+ * An authority string will be converted to a URI having the scheme of the name resolver with
+ * the highest priority (e.g. {@code "dns"}), the empty string as the authority, and
+ * {@code target} as its absolute path. We recommend libraries specify {@code target} as a URI
+ * instead since they cannot know which NameResolver will be default at runtime.
* Example authority strings:
* The URI form of {@code target} is preferred because it is less ambiguous. For example, the
+ * target string {@code foo:8080} is a valid authority string with host {@code foo} and port
+ * {@code 8080} but it is also a valid RFC 3986 URI with scheme {@code foo} and path {@code 8080}.
+ * gRPC prioritizes the URI form, which means {@code foo:8080} will be treated as a URI with
+ * scheme {@code foo}. Using {@code dns:///foo:8080} avoids this ambiguity.
+ *
* Note that there is an open JDK bug on {@link java.net.URI} class parsing an ipv6 scope ID:
* bugs.openjdk.org/browse/JDK-8199396. This method is exposed to this bug. If you experience an
* issue, a work-around is to convert the scope ID to its numeric form (e.g. by using
@@ -87,12 +92,13 @@ public static ManagedChannelBuilder> forTarget(String target) {
}
/**
- * Execute application code directly in the transport thread.
- *
- * Depending on the underlying transport, using a direct executor may lead to substantial
- * performance improvements. However, it also requires the application to not block under
+ * Execute application code directly in the transport thread. The application must not block under
* any circumstances.
*
+ * Depending on the underlying transport and the application code, using a direct executor may
+ * lead to 10s of µs latency reduction but causes a substantial performance degradation when
+ * misused.
+ *
* Calling this method is semantically equivalent to calling {@link #executor(Executor)} and
* passing in a direct executor. However, this is the preferred way as it may allow the transport
* to perform special optimizations.
@@ -103,7 +109,10 @@ public static ManagedChannelBuilder> forTarget(String target) {
public abstract T directExecutor();
/**
- * Provides a custom executor.
+ * Set the default executor for callbacks. This is used for async and future stub callbacks, but
+ * can be overridden by {@link CallOptions#withExecutor} and {@code stub.withExecutor()}. Blocking
+ * stubs specify a per-RPC executor. This is also used for {@link
+ * ManagedChannel#notifyWhenStateChanged}.
*
* It's an optional parameter. If the user has not provided an executor when the channel is
* built, the builder will use a static cached thread pool.
@@ -374,9 +383,17 @@ public T maxInboundMetadataSize(int bytes) {
* notice when they are causing excessive load. Clients are strongly encouraged to use only as
* small of a value as necessary.
*
+ * When the channel implementation supports TCP_USER_TIMEOUT, enabling keepalive will also
+ * enable TCP_USER_TIMEOUT for the connection. This requires all sent packets to receive
+ * a TCP acknowledgement before the keepalive timeout. The keepalive time is not used for
+ * TCP_USER_TIMEOUT, except as a signal to enable the feature. grpc-netty supports
+ * TCP_USER_TIMEOUT on Linux platforms supported by netty-transport-native-epoll.
+ *
* @throws UnsupportedOperationException if unsupported
* @see gRFC A8
* Client-side Keepalive
+ * @see gRFC A18
+ * TCP User Timeout
* @since 1.7.0
*/
public T keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
@@ -393,6 +410,8 @@ public T keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
* @throws UnsupportedOperationException if unsupported
* @see gRFC A8
* Client-side Keepalive
+ * @see gRFC A18
+ * TCP User Timeout
* @since 1.7.0
*/
public T keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) {
@@ -633,6 +652,40 @@ protected T addMetricSink(MetricSink metricSink) {
throw new UnsupportedOperationException();
}
+ /**
+ * Provides a "custom" argument for the {@link NameResolver}, if applicable, replacing any 'value'
+ * previously provided for 'key'.
+ *
+ * NB: If the selected {@link NameResolver} does not understand 'key', or target URI resolution
+ * isn't needed at all, your custom argument will be silently ignored.
+ *
+ * See {@link NameResolver.Args#getArg(NameResolver.Args.Key)} for more.
+ *
+ * @param key identifies the argument in a type-safe manner
+ * @param value the argument itself
+ * @return this
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
+ public This allows injecting universal configuration (like interceptors)
+ * into auxiliary channels created by gRPC infrastructure, such as xDS control plane connections.
+ *
+ * @param channelConfigurator the configurator to apply.
+ * @return this
+ * @since 1.83.0
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/12574")
+ public T childChannelConfigurator(ChannelConfigurator channelConfigurator) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
/**
* Builds a channel using the given parameters.
diff --git a/api/src/main/java/io/grpc/ManagedChannelProvider.java b/api/src/main/java/io/grpc/ManagedChannelProvider.java
index 42941dfc809..8a61c097ed8 100644
--- a/api/src/main/java/io/grpc/ManagedChannelProvider.java
+++ b/api/src/main/java/io/grpc/ManagedChannelProvider.java
@@ -81,6 +81,31 @@ protected NewChannelBuilderResult newChannelBuilder(String target, ChannelCreden
return NewChannelBuilderResult.error("ChannelCredentials are unsupported");
}
+ /**
+ * Creates a channel builder using the provided target, credentials, and resolution
+ * components.
+ *
+ * This method allows for fine-grained control over name resolution by providing
+ * both a {@link NameResolverRegistry} and a specific {@link NameResolverProvider}.
+ * This returns a {@link NewChannelBuilderResult},
+ * which may contain an error string if the provided credentials or target are
+ * not supported by this provider.
+ *
+ * @param target the target URI for the channel
+ * @param creds the channel credentials to use
+ * @param nameResolverRegistry the registry used for looking up name resolvers
+ * @param nameResolverProvider a specific provider to use, or {@code null} to
+ * search the registry
+ * @return a {@link NewChannelBuilderResult} containing either the builder or an
+ * error description
+ * @since 1.83.0
+ */
+ protected NewChannelBuilderResult newChannelBuilder(String target, ChannelCredentials creds,
+ NameResolverRegistry nameResolverRegistry,
+ NameResolverProvider nameResolverProvider) {
+ return newChannelBuilder(target, creds);
+ }
+
/**
* Returns the {@link SocketAddress} types this ManagedChannelProvider supports.
*/
diff --git a/api/src/main/java/io/grpc/ManagedChannelRegistry.java b/api/src/main/java/io/grpc/ManagedChannelRegistry.java
index 31f874b8094..c70b6812651 100644
--- a/api/src/main/java/io/grpc/ManagedChannelRegistry.java
+++ b/api/src/main/java/io/grpc/ManagedChannelRegistry.java
@@ -18,6 +18,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
+import com.google.errorprone.annotations.concurrent.GuardedBy;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
@@ -28,9 +29,9 @@
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.ServiceLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
-import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
/**
@@ -100,8 +101,10 @@ public static synchronized ManagedChannelRegistry getDefaultRegistry() {
if (instance == null) {
List An instance may not be started more than once, by any overload of this method, even after
* an intervening call to {@link #shutdown}.
*
@@ -95,7 +96,14 @@ public void onError(Status error) {
@Override
public void onResult(ResolutionResult resolutionResult) {
- listener.onAddresses(resolutionResult.getAddresses(), resolutionResult.getAttributes());
+ StatusOr An instance may not be started more than once, by any overload of this method, even after
* an intervening call to {@link #shutdown}.
*
@@ -149,6 +157,10 @@ public abstract static class Factory {
* cannot be resolved by this factory. The decision should be solely based on the scheme of the
* URI.
*
+ * This method will eventually be deprecated and removed as part of a migration from {@code
+ * java.net.URI} to {@code io.grpc.Uri}. Implementations will override {@link
+ * #newNameResolver(Uri, Args)} instead.
+ *
* @param targetUri the target URI to be resolved, whose scheme must not be {@code null}
* @param args other information that may be useful
*
@@ -156,6 +168,37 @@ public abstract static class Factory {
*/
public abstract NameResolver newNameResolver(URI targetUri, final Args args);
+ /**
+ * Creates a {@link NameResolver} for the given target URI.
+ *
+ * Implementations return {@code null} if 'targetUri' cannot be resolved by this factory. The
+ * decision should be solely based on the target's scheme.
+ *
+ * All {@link NameResolver.Factory} implementations should override this method, as it will
+ * eventually replace {@link #newNameResolver(URI, Args)}. For backwards compatibility, this
+ * default implementation delegates to {@link #newNameResolver(URI, Args)} if 'targetUri' can be
+ * converted to a java.net.URI.
+ *
+ * NB: Conversion is not always possible, for example {@code scheme:#frag} is a valid {@link
+ * Uri} but not a valid {@link URI} because its path is empty. The default implementation throws
+ * IllegalArgumentException in these cases.
+ *
+ * @param targetUri the target URI to be resolved
+ * @param args other information that may be useful
+ * @throws IllegalArgumentException if targetUri does not have the expected form
+ * @since 1.79
+ */
+ public NameResolver newNameResolver(Uri targetUri, final Args args) {
+ // Not every io.grpc.Uri can be converted but in the ordinary ManagedChannel creation flow,
+ // any IllegalArgumentException thrown here would have happened anyway, just earlier. That's
+ // because parse/toString is transparent so java.net.URI#create here sees the original target
+ // string just like it did before the io.grpc.Uri migration.
+ //
+ // Throwing IAE shouldn't surprise non-framework callers either. After all, many existing
+ // Factory impls are picky about targetUri and throw IAE when it doesn't look how they expect.
+ return newNameResolver(URI.create(targetUri.toString()), args);
+ }
+
/**
* Returns the default scheme, which will be used to construct a URI when {@link
* ManagedChannelBuilder#forTarget(String)} is given an authority string instead of a compliant
@@ -171,10 +214,11 @@ public abstract static class Factory {
*
* All methods are expected to return quickly.
*
+ * This interface is thread-safe.
+ *
* @since 1.0.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
- @ThreadSafe
public interface Listener {
/**
* Handles updates on resolved addresses and attributes.
@@ -218,19 +262,26 @@ public abstract static class Listener2 implements Listener {
@Override
@Deprecated
@InlineMe(
- replacement = "this.onResult(ResolutionResult.newBuilder().setAddresses(servers)"
- + ".setAttributes(attributes).build())",
- imports = "io.grpc.NameResolver.ResolutionResult")
+ replacement = "this.onResult(ResolutionResult.newBuilder().setAddressesOrError("
+ + "StatusOr.fromValue(servers)).setAttributes(attributes).build())",
+ imports = {"io.grpc.NameResolver.ResolutionResult", "io.grpc.StatusOr"})
public final void onAddresses(
List Newer NameResolver implementations should prefer calling onResult2. This method exists to
+ * facilitate older {@link Listener} implementations to migrate to {@link Listener2}.
*
* @param resolutionResult the resolved server addresses, attributes, and Service Config.
* @since 1.21.0
@@ -241,6 +292,10 @@ public final void onAddresses(
* Handles a name resolving error from the resolver. The listener is responsible for eventually
* invoking {@link NameResolver#refresh()} to re-attempt resolution.
*
+ * New NameResolver implementations should prefer calling onResult2 which will have the
+ * address resolution error in {@link ResolutionResult}'s addressesOrError. This method exists
+ * to facilitate older implementations using {@link Listener} to migrate to {@link Listener2}.
+ *
* @param error a non-OK status
* @since 1.21.0
*/
@@ -248,9 +303,14 @@ public final void onAddresses(
public abstract void onError(Status error);
/**
- * Handles updates on resolved addresses and attributes.
+ * Handles updates on resolved addresses and attributes. Must be called from the same
+ * {@link SynchronizationContext} available in {@link NameResolver.Args} that is passed
+ * from the channel.
*
- * @param resolutionResult the resolved server addresses, attributes, and Service Config.
+ * @param resolutionResult the resolved server addresses or error in address resolution,
+ * attributes, and Service Config or error
+ * @return status indicating whether the resolutionResult was accepted by the listener,
+ * typically the result from a load balancer.
* @since 1.66
*/
public Status onResult2(ResolutionResult resolutionResult) {
@@ -268,10 +328,20 @@ public Status onResult2(ResolutionResult resolutionResult) {
@Documented
public @interface ResolutionResultAttr {}
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11989")
+ @ResolutionResultAttr
+ public static final Attributes.Key Note this class doesn't override neither {@code equals()} nor {@code hashCode()}.
+ * Args applicable to all {@link NameResolver}s are defined here using ordinary setters and
+ * getters. This container can also hold externally-defined "custom" args that aren't so widely
+ * useful or that would be inappropriate dependencies for this low level API. See {@link
+ * Args#getArg} for more.
+ *
+ * Note this class overrides neither {@code equals()} nor {@code hashCode()}.
*
* @since 1.21.0
*/
@@ -285,24 +355,26 @@ public static final class Args {
@Nullable private final ChannelLogger channelLogger;
@Nullable private final Executor executor;
@Nullable private final String overrideAuthority;
-
- private Args(
- Integer defaultPort,
- ProxyDetector proxyDetector,
- SynchronizationContext syncContext,
- ServiceConfigParser serviceConfigParser,
- @Nullable ScheduledExecutorService scheduledExecutorService,
- @Nullable ChannelLogger channelLogger,
- @Nullable Executor executor,
- @Nullable String overrideAuthority) {
- this.defaultPort = checkNotNull(defaultPort, "defaultPort not set");
- this.proxyDetector = checkNotNull(proxyDetector, "proxyDetector not set");
- this.syncContext = checkNotNull(syncContext, "syncContext not set");
- this.serviceConfigParser = checkNotNull(serviceConfigParser, "serviceConfigParser not set");
- this.scheduledExecutorService = scheduledExecutorService;
- this.channelLogger = channelLogger;
- this.executor = executor;
- this.overrideAuthority = overrideAuthority;
+ private final MetricRecorder metricRecorder;
+ @Nullable private final NameResolverRegistry nameResolverRegistry;
+ @Nullable private final IdentityHashMap TODO: Only meaningful for InetSocketAddress producers. Make this a custom arg?
public int getDefaultPort() {
return defaultPort;
}
@@ -363,6 +436,30 @@ public ServiceConfigParser getServiceConfigParser() {
return serviceConfigParser;
}
+ /**
+ * Returns the value of a custom arg named 'key', or {@code null} if it's not set.
+ *
+ * While ordinary {@link Args} should be universally useful and meaningful, custom arguments
+ * can apply just to resolvers of a certain URI scheme, just to resolvers producing a particular
+ * type of {@link java.net.SocketAddress}, or even an individual {@link NameResolver} subclass.
+ * Custom args are identified by an instance of {@link Args.Key} which should be a constant
+ * defined in a java package and class appropriate for the argument's scope.
+ *
+ * {@link Args} are normally reserved for information in *support* of name resolution, not
+ * the name to be resolved itself. However, there are rare cases where all or part of the target
+ * name can't be represented by any standard URI scheme or can't be encoded as a String at all.
+ * Custom args, in contrast, can hold arbitrary Java types, making them a useful work around in
+ * these cases.
+ *
+ * Custom args can also be used simply to avoid adding inappropriate deps to the low level
+ * io.grpc package.
+ */
+ @SuppressWarnings("unchecked") // Cast is safe because all put()s go through the setArg() API.
+ @Nullable
+ public Uses reference equality so keys should be defined as global constants.
+ *
+ * @param For example, a URI like {@code "http://who?name=John+Doe&role=admin&role=user&active"} has:
+ *
+ * This class is meant to be used with {@link io.grpc.Uri}. For example:
+ *
+ * Note that the empty collection is encoded as a null raw query string, which means "absent" to
+ * {@link io.grpc.Uri.Builder#setRawQuery}. An empty string query component (""), on the other hand,
+ * is modeled as an instance of QueryParams containing a single lone (empty) key. It must be this
+ * way if we are to simultaneously 1) support lone keys, 2) have parse/toRawQuery round-trip
+ * transparency, and 3) never fail to parse a valid RFC 3986 query component.
+ *
+ * This container and its {@link Entry} take the same position as {@link io.grpc.Uri} on
+ * equality: raw keys and values must match exactly to be equal. Most callers won't care about how
+ * keys and values are encoded on the wire and will work with the getters for cooked keys and values
+ * instead.
+ *
+ * Instances are not safe for concurrent access by multiple threads, including by way of the
+ * {@link #asList()} view method.
+ */
+@Internal
+public final class QueryParams {
+
+ private static final String UTF_8 = "UTF-8";
+ private final List The input is split on {@code '&'} and each parameter is parsed as either a key/value pair
+ * (if it contains an equals sign) or a "lone" key (if it does not).
+ *
+ * No valid RFC 3986 query component will fail to parse. For example, {@code ===} is parsed as
+ * a single parameter with "" as the key and "==" as the value. {@code &&&} is parsed as three
+ * lone keys named "". And so on. If {@code rawQuery} is not a valid RFC 3986 query component, the
+ * behavior is undefined. But if you are starting with a {@link io.grpc.Uri}, passing the value
+ * returned by {@link io.grpc.Uri#getRawQuery()} is always well-defined and will never fail.
+ *
+ * Calling {@link #toRawQuery()} on the returned object is guaranteed to return exactly {@code
+ * rawQuery}.
+ *
+ * @param rawQuery the raw query component to parse, or null to return an empty container
+ * @return a new instance of {@code QueryParams} representing the input
+ */
+ public static QueryParams fromRawQuery(@Nullable String rawQuery) {
+ QueryParams params = new QueryParams();
+ if (rawQuery != null) {
+ for (String part : Splitter.on('&').split(rawQuery)) {
+ int equalsIndex = part.indexOf('=');
+ if (equalsIndex == -1) {
+ params.entries.add(Entry.forRawLoneKey(part));
+ } else {
+ String rawKey = part.substring(0, equalsIndex);
+ String rawValue = part.substring(equalsIndex + 1);
+ params.entries.add(Entry.forRawKeyValue(rawKey, rawValue));
+ }
+ }
+ }
+ return params;
+ }
+
+ /**
+ * Returns a mutable list view of the query parameters.
+ *
+ * @return the mutable list of entries
+ */
+ public List Any characters that needed URL encoding have already been decoded.
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * Returns the value, or {@code null} if this is a "lone" key.
+ *
+ * Any characters that needed URL encoding have already been decoded.
+ */
+ @Nullable
+ public String getValue() {
+ return value;
+ }
+
+ /** Returns {@code true} if this entry has a value, {@code false} if it is a "lone" key. */
+ public boolean hasValue() {
+ return value != null;
+ }
+
+ /**
+ * Creates a new key/value pair entry.
+ *
+ * Both key and value can contain any character. They will be URL encoded for you if
+ * necessary.
+ */
+ public static Entry forKeyValue(String key, String value) {
+ checkNotNull(key, "key");
+ checkNotNull(value, "value");
+ return new Entry(encode(key), encode(value), key, value);
+ }
+
+ /**
+ * Creates a new query parameter with a "lone" key.
+ *
+ * 'key' can contain any character. It will be URL encoded for you later, as necessary.
+ *
+ * @param key the decoded key, must not be null
+ * @return a new {@code Entry}
+ */
+ public static Entry forLoneKey(String key) {
+ checkNotNull(key, "key");
+ return new Entry(encode(key), null, key, null);
+ }
+
+ static Entry forRawKeyValue(String rawKey, String rawValue) {
+ checkNotNull(rawKey, "rawKey");
+ checkNotNull(rawValue, "rawValue");
+ return new Entry(rawKey, rawValue, decode(rawKey), decode(rawValue));
+ }
+
+ static Entry forRawLoneKey(String rawKey) {
+ checkNotNull(rawKey, "rawKey");
+ return new Entry(rawKey, null, decode(rawKey), null);
+ }
+
+ void appendToRawQueryStringBuilder(StringBuilder sb) {
+ sb.append(rawKey);
+ if (rawValue != null) {
+ sb.append('=').append(rawValue);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Entry)) {
+ return false;
+ }
+ Entry entry = (Entry) o;
+ return Objects.equals(rawKey, entry.rawKey) && Objects.equals(rawValue, entry.rawValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(rawKey, rawValue);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ appendToRawQueryStringBuilder(sb);
+ return sb.toString();
+ }
+ }
+
+ private static String decode(String s) {
+ try {
+ // TODO: Use URLDecoder.decode(String, Charset) when available
+ return URLDecoder.decode(s, UTF_8);
+ } catch (UnsupportedEncodingException impossible) {
+ throw new AssertionError("UTF-8 is not supported", impossible);
+ }
+ }
+
+ private static String encode(String s) {
+ try {
+ // TODO: Use URLEncoder.encode(String, Charset) when available
+ return URLEncoder.encode(s, UTF_8);
+ } catch (UnsupportedEncodingException impossible) {
+ throw new AssertionError("UTF-8 is not supported", impossible);
+ }
+ }
+}
diff --git a/api/src/main/java/io/grpc/Server.java b/api/src/main/java/io/grpc/Server.java
index 97ea06a81c2..97c4d495b8a 100644
--- a/api/src/main/java/io/grpc/Server.java
+++ b/api/src/main/java/io/grpc/Server.java
@@ -21,13 +21,13 @@
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
-import javax.annotation.concurrent.ThreadSafe;
/**
* Server for listening for and dispatching incoming calls. It is not expected to be implemented by
* application code or interceptors.
+ *
+ * This class is thread-safe.
*/
-@ThreadSafe
public abstract class Server {
/**
diff --git a/api/src/main/java/io/grpc/ServerBuilder.java b/api/src/main/java/io/grpc/ServerBuilder.java
index cd1cddbb93f..73521cd117d 100644
--- a/api/src/main/java/io/grpc/ServerBuilder.java
+++ b/api/src/main/java/io/grpc/ServerBuilder.java
@@ -45,12 +45,13 @@ public static ServerBuilder> forPort(int port) {
}
/**
- * Execute application code directly in the transport thread.
- *
- * Depending on the underlying transport, using a direct executor may lead to substantial
- * performance improvements. However, it also requires the application to not block under
+ * Execute application code directly in the transport thread. The application must not block under
* any circumstances.
*
+ * Depending on the underlying transport and the application code, using a direct executor may
+ * lead to 10s of µs latency reduction but causes a substantial performance degradation when
+ * misused.
+ *
* Calling this method is semantically equivalent to calling {@link #executor(Executor)} and
* passing in a direct executor. However, this is the preferred way as it may allow the transport
* to perform special optimizations.
@@ -61,10 +62,11 @@ public static ServerBuilder> forPort(int port) {
public abstract T directExecutor();
/**
- * Provides a custom executor.
+ * Set the default executor for service callbacks.
*
* It's an optional parameter. If the user has not provided an executor when the server is
- * built, the builder will use a static cached thread pool.
+ * built, the builder will use a static cached thread pool. Users are encouraged to specify their
+ * own executor that limits the number of threads.
*
* The server won't take ownership of the given executor. It's caller's responsibility to
* shut down the executor when it's desired.
@@ -85,11 +87,13 @@ public static ServerBuilder> forPort(int port) {
* it switches over. But if calling {@link ServerCallExecutorSupplier} returns null, the server
* call is still handled by the default {@link #executor(Executor)} as a fallback.
*
+ * If your {@code executorSupplier} runs quickly and always returns a non-{@code null}
+ * executor, then you may want to use {@link #directExecutor} to reduce latency.
+ *
* @param executorSupplier the server call executor provider
* @return this
* @since 1.39.0
- *
- * */
+ */
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8274")
public T callExecutor(ServerCallExecutorSupplier executorSupplier) {
return thisT();
@@ -435,6 +439,17 @@ public T setBinaryLog(BinaryLog binaryLog) {
*/
public abstract Server build();
+ /**
+ * Adds a metric sink to the server.
+ *
+ * @param metricSink the metric sink to add.
+ * @return this
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/12693")
+ public T addMetricSink(MetricSink metricSink) {
+ return thisT();
+ }
+
/**
* Returns the correctly typed version of the builder.
*/
diff --git a/api/src/main/java/io/grpc/ServerCallHandler.java b/api/src/main/java/io/grpc/ServerCallHandler.java
index fdfa9997957..7d7d8217300 100644
--- a/api/src/main/java/io/grpc/ServerCallHandler.java
+++ b/api/src/main/java/io/grpc/ServerCallHandler.java
@@ -16,13 +16,12 @@
package io.grpc;
-import javax.annotation.concurrent.ThreadSafe;
-
/**
* Interface to initiate processing of incoming remote calls. Advanced applications and generated
* code will implement this interface to allows {@link Server}s to invoke service methods.
+ *
+ * This interface is thread-safe.
*/
-@ThreadSafe
public interface ServerCallHandler From gRPC's perspective, interceptors don't generally exist and are more of a convenience.
+ * Convenience APIs will use {@link ServerInterceptors} to convert the interceptor into a {@code
+ * ServerCallHandler}. Thus interceptors are an extension of the application and run on the same
+ * threads and receive callbacks using the same executor.
+ *
* The interceptor may be called for multiple {@link ServerCall calls} by one or more threads
* without completing the previous ones first. Refer to the
* {@link io.grpc.ServerCall.Listener ServerCall.Listener} docs for more details regarding thread
* safety of the returned listener.
+ *
+ * This interface is thread-safe.
*/
-@ThreadSafe
public interface ServerInterceptor {
/**
* Intercept {@link ServerCall} dispatch by the {@code next} {@link ServerCallHandler}. General
diff --git a/api/src/main/java/io/grpc/ServerRegistry.java b/api/src/main/java/io/grpc/ServerRegistry.java
index a083e45a000..1ec7030b82b 100644
--- a/api/src/main/java/io/grpc/ServerRegistry.java
+++ b/api/src/main/java/io/grpc/ServerRegistry.java
@@ -18,14 +18,15 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
+import com.google.errorprone.annotations.concurrent.GuardedBy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.ServiceLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
-import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
/**
@@ -93,8 +94,9 @@ public static synchronized ServerRegistry getDefaultRegistry() {
if (instance == null) {
List This class is thread-safe.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2861")
-@ThreadSafe
public abstract class ServerStreamTracer extends StreamTracer {
/**
* Called before the interceptors and the call handlers and make changes to the Context object
diff --git a/api/src/main/java/io/grpc/ServiceProviders.java b/api/src/main/java/io/grpc/ServiceProviders.java
index ac4b27d8783..861688be9fb 100644
--- a/api/src/main/java/io/grpc/ServiceProviders.java
+++ b/api/src/main/java/io/grpc/ServiceProviders.java
@@ -17,10 +17,13 @@
package io.grpc;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Supplier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.Iterator;
import java.util.List;
+import java.util.ListIterator;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
@@ -29,42 +32,44 @@ private ServiceProviders() {
// do not instantiate
}
- /**
- * If this is not Android, returns the highest priority implementation of the class via
- * {@link ServiceLoader}.
- * If this is Android, returns an instance of the highest priority class in {@code hardcoded}.
- */
- public static {@code serviceLoader} should be created with {@code ServiceLoader.load(MyClass.class,
+ * MyClass.class.getClassLoader()).iterator()} in order to be detected by R8 so that R8 full mode
+ * will keep the constructors for the provider classes.
*/
public static DO NOT MOCK: Use TestStreamTracer. Mocks are not thread-safe
+ *
+ * This class is thread-safe.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2861")
-@ThreadSafe
public abstract class StreamTracer {
/**
* Stream is closed. This will be called exactly once.
diff --git a/api/src/main/java/io/grpc/SynchronizationContext.java b/api/src/main/java/io/grpc/SynchronizationContext.java
index 5a7677ac15f..94916a1b473 100644
--- a/api/src/main/java/io/grpc/SynchronizationContext.java
+++ b/api/src/main/java/io/grpc/SynchronizationContext.java
@@ -18,8 +18,10 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
+import static io.grpc.TimeUtils.convertToNanos;
import java.lang.Thread.UncaughtExceptionHandler;
+import java.time.Duration;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
@@ -162,6 +164,12 @@ public String toString() {
return new ScheduledHandle(runnable, future);
}
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11657")
+ public final ScheduledHandle schedule(
+ final Runnable task, Duration delay, ScheduledExecutorService timerService) {
+ return schedule(task, convertToNanos(delay), TimeUnit.NANOSECONDS, timerService);
+ }
+
/**
* Schedules a task to be added and run via {@link #execute} after an initial delay and then
* repeated after the delay until cancelled.
@@ -193,6 +201,14 @@ public String toString() {
return new ScheduledHandle(runnable, future);
}
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11657")
+ public final ScheduledHandle scheduleWithFixedDelay(
+ final Runnable task, Duration initialDelay, Duration delay,
+ ScheduledExecutorService timerService) {
+ return scheduleWithFixedDelay(task, convertToNanos(initialDelay), convertToNanos(delay),
+ TimeUnit.NANOSECONDS, timerService);
+ }
+
private static class ManagedRunnable implements Runnable {
final Runnable task;
@@ -246,4 +262,4 @@ public boolean isPending() {
return !(runnable.hasStarted || runnable.isCancelled);
}
}
-}
+}
\ No newline at end of file
diff --git a/api/src/main/java/io/grpc/TimeUtils.java b/api/src/main/java/io/grpc/TimeUtils.java
new file mode 100644
index 00000000000..01b8c158822
--- /dev/null
+++ b/api/src/main/java/io/grpc/TimeUtils.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc;
+
+import java.time.Duration;
+
+final class TimeUtils {
+ private TimeUtils() {}
+
+ @IgnoreJRERequirement
+ static long convertToNanos(Duration duration) {
+ try {
+ return duration.toNanos();
+ } catch (ArithmeticException tooBig) {
+ return duration.isNegative() ? Long.MIN_VALUE : Long.MAX_VALUE;
+ }
+ }
+}
diff --git a/api/src/main/java/io/grpc/Uri.java b/api/src/main/java/io/grpc/Uri.java
new file mode 100644
index 00000000000..07cc9198966
--- /dev/null
+++ b/api/src/main/java/io/grpc/Uri.java
@@ -0,0 +1,1170 @@
+/*
+ * Copyright 2025 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.base.VerifyException;
+import com.google.common.collect.ImmutableList;
+import com.google.common.net.InetAddresses;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import java.net.InetAddress;
+import java.net.URISyntaxException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.MalformedInputException;
+import java.nio.charset.StandardCharsets;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import javax.annotation.Nullable;
+
+/**
+ * A not-quite-general-purpose representation of a Uniform Resource Identifier (URI), as defined by
+ * RFC 3986.
+ *
+ * A URI identifies a resource by its name or location or both. The resource could be a file,
+ * service, or some other abstract entity.
+ *
+ * This class aims to meet the needs of grpc-java itself and RPC related code that depend on it.
+ * It isn't quite general-purpose. It definitely would not be suitable for building an HTTP user
+ * agent or proxy server. In particular, it:
+ *
+ * Those migrating from {@link java.net.URI} and/or its primary specification in RFC 2396 should
+ * note some differences.
+ *
+ * RFC 3986 unifies the older ideas of "hierarchical" and "opaque" URIs into a single generic
+ * syntax. What RFC 2396 called an opaque "scheme-specific part" is always broken out by RFC 3986
+ * into an authority and path hierarchy, followed by query and fragment components. Accordingly,
+ * this class has only getters for those components but no {@link
+ * java.net.URI#getSchemeSpecificPart()} analog.
+ *
+ * The RFC 3986 definition of path is now more liberal to accommodate this:
+ *
+ * The uniform syntax always understands what follows a '?' to be a query string. For example,
+ * Same goes for fragment. RFC 2396 tried to guess if an authority was a "server" (host:port) or "registry-based"
+ * (arbitrary string) based on its contents. RFC 3986 expects every authority to look like
+ * [userinfo@]host[:port] and loosens the definition of a "host" to accommodate. Accordingly, this
+ * class has no equivalent to {@link java.net.URI#parseServerAuthority()} -- authority was parsed
+ * into its components and checked for validity when the {@link Uri} was created.
+ *
+ * RFC 2396 does not allow underscores in a host name, meaning {@link java.net.URI} switches to
+ * opaque mode when it sees one. {@link Uri} does allow underscores in host, to accommodate
+ * registries other than DNS. So {@link Uri} strictly *requires* square brackets in the query string and fragment to be
+ * percent-encoded whereas RFC 2396 merely recommended doing so.
+ *
+ * Other URx classes are "liberal in what they accept and strict in what they produce." {@link
+ * Uri#parse(String)} and {@link Uri#create(String)}, however, are strict in what they accept and
+ * transparent when asked to reproduce it via {@link Uri#toString()}. The former policy may be
+ * appropriate for parsing user input or web content, but this class is meant for gRPC clients,
+ * servers and plugins like name resolvers where human error at runtime is less likely and best
+ * detected early. {@link java.net.URI#create(String)} is similarly strict, which makes migration
+ * easy, except for the server/registry-based ambiguity addressed by {@link
+ * java.net.URI#parseServerAuthority()}.
+ *
+ * {@link java.net.URI} and {@link Uri} both support IPv6 literals in square brackets as defined
+ * by RFC 2732.
+ *
+ * {@link java.net.URI} supports IPv6 scope IDs but accepts and emits a non-standard syntax.
+ * {@link Uri} implements the newer RFC 6874, which percent encodes scope IDs and the % delimiter
+ * itself. RFC 9844 claims to obsolete RFC 6874 because web browsers would not support it. This
+ * class implements RFC 6874 anyway, mostly to avoid creating a barrier to migration away from
+ * {@link java.net.URI}.
+ *
+ * Some URI components, e.g. scheme, are required while others may or may not be present, e.g.
+ * authority. {@link Uri} is careful to preserve the distinction between an absent string component
+ * (getter returns null) and one with an empty value (getter returns ""). {@link java.net.URI} makes
+ * this distinction too, *except* when it comes to the authority and host components: {@link
+ * java.net.URI#getAuthority()} and {@link java.net.URI#getHost()} return null when an authority is
+ * absent, e.g. Useful for defining URI constants in code. Not for user input.
+ *
+ * @throws IllegalArgumentException if 's' is not a valid RFC 3986 URI.
+ */
+ public static Uri create(String s) {
+ Builder builder = new Builder();
+ int i = 0;
+ final int n = s.length();
+
+ // 3.1. Scheme: Look for a ':' before '/', '?', or '#'.
+ int schemeColon = -1;
+ for (; i < n; ++i) {
+ char c = s.charAt(i);
+ if (c == ':') {
+ schemeColon = i;
+ break;
+ } else if (c == '/' || c == '?' || c == '#') {
+ break;
+ }
+ }
+ if (schemeColon < 0) {
+ throw new IllegalArgumentException("Missing required scheme.");
+ }
+ builder.setRawScheme(s.substring(0, schemeColon));
+
+ // 3.2. Authority. Look for '//' then keep scanning until '/', '?', or '#'.
+ i = schemeColon + 1;
+ if (i + 1 < n && s.charAt(i) == '/' && s.charAt(i + 1) == '/') {
+ // "//" just means we have an authority. Skip over it.
+ i += 2;
+
+ int authorityStart = i;
+ for (; i < n; ++i) {
+ char c = s.charAt(i);
+ if (c == '/' || c == '?' || c == '#') {
+ break;
+ }
+ }
+ builder.setRawAuthority(s.substring(authorityStart, i));
+ }
+
+ // 3.3. Path: Whatever is left before '?' or '#'.
+ int pathStart = i;
+ for (; i < n; ++i) {
+ char c = s.charAt(i);
+ if (c == '?' || c == '#') {
+ break;
+ }
+ }
+ builder.setRawPath(s.substring(pathStart, i));
+
+ // 3.4. Query, if we stopped at '?'.
+ if (i < n && s.charAt(i) == '?') {
+ i++; // Skip '?'
+ int queryStart = i;
+ for (; i < n; ++i) {
+ char c = s.charAt(i);
+ if (c == '#') {
+ break;
+ }
+ }
+ builder.setRawQuery(s.substring(queryStart, i));
+ }
+
+ // 3.5. Fragment, if we stopped at '#'.
+ if (i < n && s.charAt(i) == '#') {
+ ++i; // Skip '#'
+ builder.setRawFragment(s.substring(i));
+ }
+
+ return builder.build();
+ }
+
+ private static int findPortStartColon(String authority, int hostStart) {
+ for (int i = authority.length() - 1; i >= hostStart; --i) {
+ char c = authority.charAt(i);
+ if (c == ':') {
+ return i;
+ }
+ if (c == ']') {
+ // Hit the end of IP-literal. Any further colon is inside it and couldn't indicate a port.
+ break;
+ }
+ if (!digitChars.get(c)) {
+ // Found a non-digit, non-colon, non-bracket.
+ // This means there is no valid port (e.g. host is "example.com")
+ break;
+ }
+ }
+ return -1;
+ }
+
+ // Checks a raw path for validity and parses it into segments. Let 'out' be null to just validate.
+ private static void parseAssumedUtf8PathIntoSegments(
+ String path, ImmutableList.Builder NB: This method's decoding is lossy -- It only exists for compatibility with {@link
+ * java.net.URI}. Prefer {@link #getRawAuthority()} or work instead with authority in terms of its
+ * individual components ({@link #getUserInfo()}, {@link #getHost()} and {@link #getPort()}). The
+ * problem with getAuthority() is that it returns the delimited concatenation of the percent-
+ * decoded userinfo, host and port components. But both userinfo and host can contain the '@'
+ * character, which becomes indistinguishable from the userinfo/host delimiter after decoding. For
+ * example, URIs NB: This method assumes the "host" component was encoded as UTF-8, as mandated by RFC 3986.
+ * This method also assumes the "user information" part of authority was encoded as UTF-8,
+ * although RFC 3986 doesn't specify an encoding.
+ *
+ * Decoding errors are indicated by a {@code '\u005CuFFFD'} unicode replacement character in
+ * the output. Callers who want to detect and handle errors in some other way should call {@link
+ * #getRawAuthority()}, {@link #percentDecode(CharSequence)}, then decode the bytes for
+ * themselves.
+ */
+ @Nullable
+ public String getAuthority() {
+ return percentDecodeAssumedUtf8(getRawAuthority());
+ }
+
+ private boolean hasAuthority() {
+ return host != null;
+ }
+
+ /**
+ * Returns the "authority" component of this URI in its originally parsed, possibly
+ * percent-encoded form.
+ */
+ @Nullable
+ public String getRawAuthority() {
+ if (hasAuthority()) {
+ StringBuilder sb = new StringBuilder();
+ appendAuthority(sb);
+ return sb.toString();
+ }
+ return null;
+ }
+
+ private void appendAuthority(StringBuilder sb) {
+ if (userInfo != null) {
+ sb.append(userInfo).append('@');
+ }
+ if (host != null) {
+ sb.append(host);
+ }
+ if (port != null) {
+ sb.append(':').append(port);
+ }
+ }
+
+ /**
+ * Returns the percent-decoded "User Information" component of this URI, or null if not present.
+ *
+ * NB: This method *assumes* this component was encoded as UTF-8, although RFC 3986 doesn't
+ * specify an encoding.
+ *
+ * Decoding errors are indicated by a {@code '\u005CuFFFD'} unicode replacement character in
+ * the output. Callers who want to detect and handle errors in some other way should call {@link
+ * #getRawUserInfo()}, {@link #percentDecode(CharSequence)}, then decode the bytes for themselves.
+ */
+ @Nullable
+ public String getUserInfo() {
+ return percentDecodeAssumedUtf8(userInfo);
+ }
+
+ /**
+ * Returns the "User Information" component of this URI in its originally parsed, possibly
+ * percent-encoded form.
+ */
+ @Nullable
+ public String getRawUserInfo() {
+ return userInfo;
+ }
+
+ /**
+ * Returns the percent-decoded "host" component of this URI, or null if not present.
+ *
+ * This method assumes the host was encoded as UTF-8, as mandated by RFC 3986.
+ *
+ * Decoding errors are indicated by a {@code '\u005CuFFFD'} unicode replacement character in
+ * the output. Callers who want to detect and handle errors in some other way should call {@link
+ * #getRawHost()}, {@link #percentDecode(CharSequence)}, then decode the bytes for themselves.
+ */
+ @Nullable
+ public String getHost() {
+ return percentDecodeAssumedUtf8(host);
+ }
+
+ /**
+ * Returns the host component of this URI in its originally parsed, possibly percent-encoded form.
+ */
+ @Nullable
+ public String getRawHost() {
+ return host;
+ }
+
+ /** Returns the "port" component of this URI, or -1 if empty or not present. */
+ public int getPort() {
+ return port != null && !port.isEmpty() ? Integer.parseInt(port) : -1;
+ }
+
+ /** Returns the raw port component of this URI in its originally parsed form. */
+ @Nullable
+ public String getRawPort() {
+ return port;
+ }
+
+ /**
+ * Returns the (possibly empty) percent-decoded "path" component of this URI.
+ *
+ * NB: This method *assumes* the path was encoded as UTF-8, although RFC 3986 doesn't specify
+ * an encoding.
+ *
+ * Decoding errors are indicated by a {@code '\u005CuFFFD'} unicode replacement character in
+ * the output. Callers who want to detect and handle errors in some other way should call {@link
+ * #getRawPath()}, {@link #percentDecode(CharSequence)}, then decode the bytes for themselves.
+ *
+ * NB: Prefer {@link #getPathSegments()} because this method's decoding is lossy. For example,
+ * consider these (different) URIs:
+ *
+ * Calling getPath() on each returns the same string: Prefer this method over {@link #getPath()} because it preserves the distinction between
+ * segment separators and literal '/'s within a path segment.
+ *
+ * A trailing '/' delimiter in the path results in the empty string as the last element in the
+ * returned list. For example, A leading '/' delimiter cannot be detected using this method. For example, both The returned list is immutable.
+ */
+ public List
+ *
+ *
+ * {@code
+ * // 1. Define the configurator
+ * ChannelConfigurator configurator = builder -> {
+ * builder.maxInboundMessageSize(4 * 1024 * 1024);
+ * };
+ *
+ * // 2. Apply to parent channel - automatically used for ALL child channels
+ * ManagedChannel channel = ManagedChannelBuilder
+ * .forTarget("xds:///my-service")
+ * .childChannelConfigurator(configurator)
+ * .build();
+ * }
+ *
+ * processResponse().
*
- *
+ *
* call = channel.newCall(bidiStreamingMethod, callOptions);
* listener = new ClientCall.Listener<FooResponse>() {
* @Override
diff --git a/api/src/main/java/io/grpc/ClientInterceptor.java b/api/src/main/java/io/grpc/ClientInterceptor.java
index c27c31c8474..4517e90d890 100644
--- a/api/src/main/java/io/grpc/ClientInterceptor.java
+++ b/api/src/main/java/io/grpc/ClientInterceptor.java
@@ -16,7 +16,6 @@
package io.grpc;
-import javax.annotation.concurrent.ThreadSafe;
/**
* Interface for intercepting outgoing calls before they are dispatched by a {@link Channel}.
@@ -33,12 +32,19 @@
* CallCredentials}. But a {@code ClientInterceptor} could set the {@code
* CallCredentials} within the {@link CallOptions}.
*
+ *
*
*
- *
*
+ *
+ *
*
*
- *
*
*
+ * > addressesOrError =
+ resolutionResult.getAddressesOrError();
+ if (addressesOrError.hasValue()) {
+ listener.onAddresses(addressesOrError.getValue(),
+ resolutionResult.getAttributes());
+ } else {
+ listener.onError(addressesOrError.getStatus());
+ }
}
});
}
@@ -105,7 +113,7 @@ public void onResult(ResolutionResult resolutionResult) {
* Starts the resolution. The method is not supposed to throw any exceptions. That might cause the
* Channel that the name resolver is serving to crash. Errors should be propagated
* through {@link Listener2#onError}.
- *
+ *
*
> addressesOrError;
@ResolutionResultAttr
private final Attributes attributes;
@Nullable
private final ConfigOrError serviceConfig;
ResolutionResult(
- List
> addressesOrError,
@ResolutionResultAttr Attributes attributes,
ConfigOrError serviceConfig) {
- this.addresses = Collections.unmodifiableList(new ArrayList<>(addresses));
+ this.addressesOrError = addressesOrError;
this.attributes = checkNotNull(attributes, "attributes");
this.serviceConfig = serviceConfig;
}
@@ -615,7 +822,7 @@ public static Builder newBuilder() {
*/
public Builder toBuilder() {
return newBuilder()
- .setAddresses(addresses)
+ .setAddressesOrError(addressesOrError)
.setAttributes(attributes)
.setServiceConfig(serviceConfig);
}
@@ -624,9 +831,20 @@ public Builder toBuilder() {
* Gets the addresses resolved by name resolution.
*
* @since 1.21.0
+ * @deprecated Will be superseded by getAddressesOrError
*/
+ @Deprecated
public List
> getAddressesOrError() {
+ return addressesOrError;
}
/**
@@ -652,11 +870,11 @@ public ConfigOrError getServiceConfig() {
@Override
public String toString() {
- return MoreObjects.toStringHelper(this)
- .add("addresses", addresses)
- .add("attributes", attributes)
- .add("serviceConfig", serviceConfig)
- .toString();
+ ToStringHelper stringHelper = MoreObjects.toStringHelper(this);
+ stringHelper.add("addressesOrError", addressesOrError.toString());
+ stringHelper.add("attributes", attributes);
+ stringHelper.add("serviceConfigOrError", serviceConfig);
+ return stringHelper.toString();
}
/**
@@ -668,7 +886,7 @@ public boolean equals(Object obj) {
return false;
}
ResolutionResult that = (ResolutionResult) obj;
- return Objects.equal(this.addresses, that.addresses)
+ return Objects.equal(this.addressesOrError, that.addressesOrError)
&& Objects.equal(this.attributes, that.attributes)
&& Objects.equal(this.serviceConfig, that.serviceConfig);
}
@@ -678,7 +896,7 @@ public boolean equals(Object obj) {
*/
@Override
public int hashCode() {
- return Objects.hashCode(addresses, attributes, serviceConfig);
+ return Objects.hashCode(addressesOrError, attributes, serviceConfig);
}
/**
@@ -688,7 +906,8 @@ public int hashCode() {
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
public static final class Builder {
- private List
> addresses =
+ StatusOr.fromValue(Collections.emptyList());
private Attributes attributes = Attributes.EMPTY;
@Nullable
private ConfigOrError serviceConfig;
@@ -700,9 +919,21 @@ public static final class Builder {
* Sets the addresses resolved by name resolution. This field is required.
*
* @since 1.21.0
+ * @deprecated Will be superseded by setAddressesOrError
*/
+ @Deprecated
public Builder setAddresses(List
> addresses) {
+ this.addresses = checkNotNull(addresses, "StatusOr addresses cannot be null.");
return this;
}
@@ -825,4 +1056,10 @@ public String toString() {
}
}
}
+
+ @Nullable
+ private static IdentityHashMap
+ *
+ *
+ * {@code
+ * Uri uri = Uri.parse("http://who?name=John+Doe&role=admin&role=user&active");
+ * QueryParams params = QueryParams.fromRawQuery(uri.getRawQuery());
+ * params.asList().removeIf(e -> "role".equals(e.getKey()) && "admin".equals(e.getValue()));
+ *
+ * Uri modifiedUri = uri.toBuilder().setRawQuery(params.toRawQuery()).build();
+ * }
+ *
+ * The URI
+ *
+ * Examples
+ *
+ *
+ *
+ *
+ * http://admin@example.com:8080/controlpanel?filter=users#settings
+ * ftp://[2001:db8::7]/docs/report.pdf
+ * file:///My%20Computer/Documents/letter.doc
+ * dns://8.8.8.8/storage.googleapis.com
+ * mailto:John.Doe@example.com
+ * tel:+1-206-555-1212
+ * urn:isbn:978-1492082798
+ * Limitations
+ *
+ *
+ *
+ *
+ * https://google.com is the same as
+ * https://google.com:443, that file:/// is the same as
+ * file://localhost, or that joe@example.com is the same as
+ * joe@EXAMPLE.COM. No one class can or should know everything about every scheme so
+ * all this is better handled at a higher layer.
+ * Migrating from RFC 2396 and {@link java.net.URI}
+ *
+ * Uniform Hierarchical Syntax
+ *
+ *
+ *
+ *
+ *
+ * urn:isbn:978-1492082798 is isbn:978-1492082798 even though it doesn't
+ * look much like a file system path.
+ *
+ * intent:#Intent;action=MAIN;category=LAUNCHER;end is now a valid {@link Uri}. Even
+ * the scheme-only about: is now valid.
+ * mailto:me@example.com?subject=foo now has a query component whereas RFC 2396
+ * considered everything after the mailto: scheme to be opaque.
+ *
+ * data:image/png;...#xywh=0,0,10,10 now has a fragment
+ * whereas RFC 2396 considered everything after the scheme to be opaque.
+ *
+ * Uniform Authority Syntax
+ *
+ * Other Specific Differences
+ *
+ * http://my_site.com:8080/index.html now parses as a
+ * host, port and path rather than a single opaque scheme-specific part.
+ *
+ * file:/path as expected. But these methods surprisingly also return null
+ * when the authority is the empty string, e.g.file:///path. {@link Uri}'s getters
+ * correctly return null and "" in these cases, respectively, as one would expect.
+ */
+@Internal
+public final class Uri {
+ // Components are stored percent-encoded, just as originally parsed for transparent parse/toString
+ // round-tripping.
+ private final String scheme; // != null since we don't support relative references.
+ @Nullable private final String userInfo;
+ @Nullable private final String host;
+ @Nullable private final String port;
+ private final String path; // In RFC 3986, path is always defined (but can be empty).
+ @Nullable private final String query;
+ @Nullable private final String fragment;
+
+ private Uri(Builder builder) {
+ this.scheme = checkNotNull(builder.scheme, "scheme");
+ this.userInfo = builder.userInfo;
+ this.host = builder.host;
+ this.port = builder.port;
+ this.path = builder.path;
+ this.query = builder.query;
+ this.fragment = builder.fragment;
+
+ // Checks common to the parse() and Builder code paths.
+ if (hasAuthority()) {
+ if (!path.isEmpty() && !path.startsWith("/")) {
+ throw new IllegalArgumentException("Has authority -- Non-empty path must start with '/'");
+ }
+ } else {
+ if (path.startsWith("//")) {
+ throw new IllegalArgumentException("No authority -- Path cannot start with '//'");
+ }
+ }
+ }
+
+ /**
+ * Parses a URI from its string form.
+ *
+ * @throws URISyntaxException if 's' is not a valid RFC 3986 URI.
+ */
+ public static Uri parse(String s) throws URISyntaxException {
+ try {
+ return create(s);
+ } catch (IllegalArgumentException e) {
+ throw new URISyntaxException(s, e.getMessage());
+ }
+ }
+
+ /**
+ * Creates a URI from a string assumed to be valid.
+ *
+ * scheme://x@y%40z and scheme://x%40y@z have different
+ * userinfo and host components but getAuthority() returns "x@y@z" for both of them.
+ *
+ *
+ *
+ *
+ * /home/folder/my file. You
+ * can't tell whether the second '/' character is part of the first path segment or separates the
+ * first and second path segments. This method only exists to ease migration from {@link
+ * java.net.URI}.
+ */
+ public String getPath() {
+ return percentDecodeAssumedUtf8(path);
+ }
+
+ /**
+ * Returns this URI's path as a list of path segments not including the '/' segment delimiters.
+ *
+ * file://localhost/foo/bar/ has path segments
+ * ["foo", "bar", ""]
+ *
+ *
+ * dns:example.com and dns:///example.com have the same list of path segments:
+ * ["example.com"]. Use {@link #isPathAbsolute()} or {@link #isPathRootless()} to
+ * distinguish these cases.
+ *
+ *