Advanced utilities for gRPC Java providing load balancing, TLS management, and server utilities
—
Base classes for creating decorators and extensions of core gRPC components using the forwarding pattern. These utilities allow developers to add custom behavior while preserving the original functionality.
Base class for decorating load balancers with additional functionality.
/**
* Base class for load balancer decorators that forward calls to an underlying
* load balancer while allowing customization of specific behaviors.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
public abstract class ForwardingLoadBalancer extends LoadBalancer {
/**
* Returns the underlying load balancer to which calls are forwarded
* @return the delegate load balancer
*/
protected abstract LoadBalancer delegate();
/**
* Handles resolved addresses by forwarding to the delegate
* @param resolvedAddresses the resolved addresses from name resolution
*/
@Override
public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses);
/**
* Handles name resolution errors by forwarding to the delegate
* @param error the name resolution error status
*/
@Override
public void handleNameResolutionError(Status error);
/**
* Handles subchannel state changes by forwarding to the delegate
* @param subchannel the subchannel that changed state
* @param stateInfo the new state information
* @deprecated Use {@link #handleResolvedAddresses} instead
*/
@Override
@Deprecated
public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo);
/**
* Shuts down the load balancer by forwarding to the delegate
*/
@Override
public void shutdown();
/**
* Checks if the load balancer can handle empty address lists
* @return true if empty address lists can be handled
*/
@Override
public boolean canHandleEmptyAddressListFromNameResolution();
/**
* Requests connection by forwarding to the delegate
*/
@Override
public void requestConnection();
/**
* Returns string representation including delegate information
* @return string representation for debugging
*/
@Override
public String toString();
}Usage Examples:
import io.grpc.util.ForwardingLoadBalancer;
import io.grpc.LoadBalancer;
import io.grpc.Status;
// Custom load balancer that adds logging
public class LoggingLoadBalancer extends ForwardingLoadBalancer {
private final LoadBalancer delegate;
private final Logger logger;
public LoggingLoadBalancer(LoadBalancer delegate) {
this.delegate = delegate;
this.logger = Logger.getLogger(LoggingLoadBalancer.class.getName());
}
@Override
protected LoadBalancer delegate() {
return delegate;
}
@Override
public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
logger.info("Handling resolved addresses: " + resolvedAddresses.getAddresses().size() + " endpoints");
super.handleResolvedAddresses(resolvedAddresses);
}
@Override
public void handleNameResolutionError(Status error) {
logger.warning("Name resolution error: " + error);
super.handleNameResolutionError(error);
}
}
// Load balancer that adds retry logic
public class RetryingLoadBalancer extends ForwardingLoadBalancer {
private final LoadBalancer delegate;
private final int maxRetries;
public RetryingLoadBalancer(LoadBalancer delegate, int maxRetries) {
this.delegate = delegate;
this.maxRetries = maxRetries;
}
@Override
protected LoadBalancer delegate() {
return delegate;
}
@Override
public void handleNameResolutionError(Status error) {
// Custom retry logic before forwarding
if (shouldRetry(error)) {
// Implement retry mechanism
scheduleRetry();
} else {
super.handleNameResolutionError(error);
}
}
}Base class for decorating load balancer helpers with additional functionality.
/**
* Base class for load balancer helper decorators that forward calls to an
* underlying helper while allowing customization of specific behaviors.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
public abstract class ForwardingLoadBalancerHelper extends LoadBalancer.Helper {
/**
* Returns the underlying helper to which calls are forwarded
* @return the delegate helper
*/
protected abstract Helper delegate();
/**
* Creates a subchannel by forwarding to the delegate
* @param args arguments for subchannel creation
* @return new Subchannel instance
*/
@Override
public Subchannel createSubchannel(CreateSubchannelArgs args);
/**
* Creates out-of-band channel by forwarding to the delegate
* @param eag equivalent address group for the channel
* @param authority authority for the channel
* @return new ManagedChannel instance
*/
@Override
public ManagedChannel createOobChannel(EquivalentAddressGroup eag, String authority);
/**
* Creates out-of-band channel with multiple address groups
* @param eag list of equivalent address groups
* @param authority authority for the channel
* @return new ManagedChannel instance
*/
@Override
public ManagedChannel createOobChannel(List<EquivalentAddressGroup> eag, String authority);
/**
* Updates out-of-band channel addresses
* @param channel the channel to update
* @param eag new equivalent address group
*/
@Override
public void updateOobChannelAddresses(ManagedChannel channel, EquivalentAddressGroup eag);
/**
* Updates out-of-band channel addresses with multiple groups
* @param channel the channel to update
* @param eag list of new equivalent address groups
*/
@Override
public void updateOobChannelAddresses(ManagedChannel channel, List<EquivalentAddressGroup> eag);
/**
* Creates resolving out-of-band channel builder (deprecated)
* @param target target URI for name resolution
* @return ManagedChannelBuilder instance
* @deprecated Use {@link #createResolvingOobChannelBuilder(String, ChannelCredentials)} instead
*/
@Override
@Deprecated
public ManagedChannelBuilder<?> createResolvingOobChannelBuilder(String target);
/**
* Creates resolving out-of-band channel builder with credentials
* @param target target URI for name resolution
* @param creds channel credentials
* @return ManagedChannelBuilder instance
*/
@Override
public ManagedChannelBuilder<?> createResolvingOobChannelBuilder(String target, ChannelCredentials creds);
/**
* Creates resolving out-of-band channel
* @param target target URI for name resolution
* @return new ManagedChannel instance
*/
@Override
public ManagedChannel createResolvingOobChannel(String target);
/**
* Updates the load balancing state
* @param newState new connectivity state
* @param newPicker new subchannel picker
*/
@Override
public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker);
/**
* Refreshes name resolution by forwarding to the delegate
*/
@Override
public void refreshNameResolution();
/**
* Ignores refresh name resolution check (deprecated)
* @deprecated No longer used
*/
@Override
@Deprecated
public void ignoreRefreshNameResolutionCheck();
/**
* Gets the channel authority
* @return authority string
*/
@Override
public String getAuthority();
/**
* Gets the channel target
* @return target string
*/
@Override
public String getChannelTarget();
/**
* Gets the channel credentials
* @return ChannelCredentials instance
*/
@Override
public ChannelCredentials getChannelCredentials();
/**
* Gets unsafe channel credentials for testing
* @return ChannelCredentials instance
*/
@Override
public ChannelCredentials getUnsafeChannelCredentials();
/**
* Gets the synchronization context
* @return SynchronizationContext instance
*/
@Override
public SynchronizationContext getSynchronizationContext();
/**
* Gets the scheduled executor service
* @return ScheduledExecutorService instance
*/
@Override
public ScheduledExecutorService getScheduledExecutorService();
/**
* Gets the channel logger
* @return ChannelLogger instance
*/
@Override
public ChannelLogger getChannelLogger();
/**
* Gets the name resolver arguments
* @return NameResolver.Args instance
*/
@Override
public NameResolver.Args getNameResolverArgs();
/**
* Gets the name resolver registry
* @return NameResolverRegistry instance
*/
@Override
public NameResolverRegistry getNameResolverRegistry();
/**
* Gets the metric recorder
* @return MetricRecorder instance
*/
@Override
public MetricRecorder getMetricRecorder();
/**
* Returns string representation including delegate information
* @return string representation for debugging
*/
@Override
public String toString();
}Usage Examples:
import io.grpc.util.ForwardingLoadBalancerHelper;
import io.grpc.LoadBalancer.Helper;
import io.grpc.LoadBalancer.Subchannel;
import io.grpc.ConnectivityState;
import io.grpc.LoadBalancer.SubchannelPicker;
// Helper that adds metrics collection
public class MetricsCollectingHelper extends ForwardingLoadBalancerHelper {
private final Helper delegate;
private final MetricsCollector metrics;
public MetricsCollectingHelper(Helper delegate, MetricsCollector metrics) {
this.delegate = delegate;
this.metrics = metrics;
}
@Override
protected Helper delegate() {
return delegate;
}
@Override
public Subchannel createSubchannel(CreateSubchannelArgs args) {
metrics.incrementSubchannelCreations();
return super.createSubchannel(args);
}
@Override
public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
metrics.recordStateChange(newState);
super.updateBalancingState(newState, newPicker);
}
}
// Helper that adds connection pooling
public class PoolingHelper extends ForwardingLoadBalancerHelper {
private final Helper delegate;
private final Map<EquivalentAddressGroup, ManagedChannel> channelPool = new ConcurrentHashMap<>();
public PoolingHelper(Helper delegate) {
this.delegate = delegate;
}
@Override
protected Helper delegate() {
return delegate;
}
@Override
public ManagedChannel createOobChannel(EquivalentAddressGroup eag, String authority) {
return channelPool.computeIfAbsent(eag,
key -> super.createOobChannel(key, authority));
}
}Base class for decorating subchannels with additional functionality.
/**
* Base class for subchannel decorators that forward calls to an underlying
* subchannel while allowing customization of specific behaviors.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
public abstract class ForwardingSubchannel extends LoadBalancer.Subchannel {
/**
* Returns the underlying subchannel to which calls are forwarded
* @return the delegate subchannel
*/
protected abstract Subchannel delegate();
/**
* Starts the subchannel with a state listener
* @param listener the subchannel state listener
*/
@Override
public void start(SubchannelStateListener listener);
/**
* Shuts down the subchannel by forwarding to the delegate
*/
@Override
public void shutdown();
/**
* Requests connection by forwarding to the delegate
*/
@Override
public void requestConnection();
/**
* Gets all addresses by forwarding to the delegate
* @return list of equivalent address groups
*/
@Override
public List<EquivalentAddressGroup> getAllAddresses();
/**
* Gets attributes by forwarding to the delegate
* @return Attributes instance
*/
@Override
public Attributes getAttributes();
/**
* Converts subchannel to channel by forwarding to the delegate
* @return Channel instance
*/
@Override
public Channel asChannel();
/**
* Gets the channel logger by forwarding to the delegate
* @return ChannelLogger instance
*/
@Override
public ChannelLogger getChannelLogger();
/**
* Gets internal subchannel object by forwarding to the delegate
* @return internal subchannel object
*/
@Override
public Object getInternalSubchannel();
/**
* Updates addresses by forwarding to the delegate
* @param addrs new list of equivalent address groups
*/
@Override
public void updateAddresses(List<EquivalentAddressGroup> addrs);
/**
* Gets connected address attributes by forwarding to the delegate
* @return Attributes of the connected address
*/
@Override
public Attributes getConnectedAddressAttributes();
/**
* Returns string representation including delegate information
* @return string representation for debugging
*/
@Override
public String toString();
}Usage Examples:
import io.grpc.util.ForwardingSubchannel;
import io.grpc.LoadBalancer.Subchannel;
import io.grpc.LoadBalancer.SubchannelStateListener;
import io.grpc.ConnectivityStateInfo;
// Subchannel that adds connection retry logic
public class RetryingSubchannel extends ForwardingSubchannel {
private final Subchannel delegate;
private final RetryPolicy retryPolicy;
public RetryingSubchannel(Subchannel delegate, RetryPolicy retryPolicy) {
this.delegate = delegate;
this.retryPolicy = retryPolicy;
}
@Override
protected Subchannel delegate() {
return delegate;
}
@Override
public void start(SubchannelStateListener listener) {
super.start(new RetryingStateListener(listener, retryPolicy));
}
private static class RetryingStateListener implements SubchannelStateListener {
private final SubchannelStateListener delegate;
private final RetryPolicy retryPolicy;
RetryingStateListener(SubchannelStateListener delegate, RetryPolicy retryPolicy) {
this.delegate = delegate;
this.retryPolicy = retryPolicy;
}
@Override
public void onSubchannelState(ConnectivityStateInfo newState) {
if (newState.getState() == ConnectivityState.TRANSIENT_FAILURE && retryPolicy.shouldRetry()) {
// Schedule retry
scheduleRetry();
} else {
delegate.onSubchannelState(newState);
}
}
}
}
// Subchannel that adds health checking
public class HealthCheckingSubchannel extends ForwardingSubchannel {
private final Subchannel delegate;
private final HealthChecker healthChecker;
public HealthCheckingSubchannel(Subchannel delegate, HealthChecker healthChecker) {
this.delegate = delegate;
this.healthChecker = healthChecker;
}
@Override
protected Subchannel delegate() {
return delegate;
}
@Override
public void start(SubchannelStateListener listener) {
super.start(new HealthCheckingStateListener(listener, healthChecker));
}
}Base class for decorating client stream tracers with additional functionality.
/**
* Base class for client stream tracer decorators that forward calls to an
* underlying tracer while allowing customization of specific behaviors.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2861")
public abstract class ForwardingClientStreamTracer extends ClientStreamTracer {
/**
* Returns the underlying tracer to which calls are forwarded
* @return the delegate tracer
*/
protected abstract ClientStreamTracer delegate();
/**
* Called when stream is created, forwards to delegate
* @param transportAttrs transport attributes
* @param headers request headers
*/
@Override
public void streamCreated(Attributes transportAttrs, Metadata headers);
/**
* Creates pending stream by forwarding to the delegate
* @return PendingStream instance
*/
@Override
protected PendingStream createPendingStream();
/**
* Gets outbound headers by forwarding to the delegate
* @return request headers
*/
@Override
public Metadata outboundHeaders();
/**
* Called on inbound headers, forwards to delegate
*/
@Override
public void inboundHeaders();
/**
* Called on inbound headers with metadata, forwards to delegate
* @param headers response headers
*/
@Override
public void inboundHeaders(Metadata headers);
/**
* Called on inbound trailers, forwards to delegate
* @param trailers response trailers
*/
@Override
public void inboundTrailers(Metadata trailers);
/**
* Adds optional label by forwarding to delegate
* @param key label key
* @param value label value
*/
@Override
public void addOptionalLabel(String key, String value);
/**
* Called when stream is closed, forwards to delegate
* @param status final status of the stream
*/
@Override
public void streamClosed(Status status);
/**
* Called on outbound message, forwards to delegate
* @param seqNo message sequence number
*/
@Override
public void outboundMessage(int seqNo);
/**
* Called on inbound message, forwards to delegate
* @param seqNo message sequence number
*/
@Override
public void inboundMessage(int seqNo);
/**
* Called when outbound message is sent, forwards to delegate
* @param seqNo message sequence number
* @param optionalWireSize wire size in bytes (or -1 if unknown)
* @param optionalUncompressedSize uncompressed size in bytes (or -1 if unknown)
*/
@Override
public void outboundMessageSent(int seqNo, long optionalWireSize, long optionalUncompressedSize);
/**
* Called when inbound message is read, forwards to delegate
* @param seqNo message sequence number
* @param optionalWireSize wire size in bytes (or -1 if unknown)
* @param optionalUncompressedSize uncompressed size in bytes (or -1 if unknown)
*/
@Override
public void inboundMessageRead(int seqNo, long optionalWireSize, long optionalUncompressedSize);
/**
* Records outbound wire size, forwards to delegate
* @param bytes number of bytes
*/
@Override
public void outboundWireSize(long bytes);
/**
* Records outbound uncompressed size, forwards to delegate
* @param bytes number of bytes
*/
@Override
public void outboundUncompressedSize(long bytes);
/**
* Records inbound wire size, forwards to delegate
* @param bytes number of bytes
*/
@Override
public void inboundWireSize(long bytes);
/**
* Records inbound uncompressed size, forwards to delegate
* @param bytes number of bytes
*/
@Override
public void inboundUncompressedSize(long bytes);
/**
* Returns string representation including delegate information
* @return string representation for debugging
*/
@Override
public String toString();
}Usage Examples:
import io.grpc.util.ForwardingClientStreamTracer;
import io.grpc.ClientStreamTracer;
import io.grpc.Metadata;
import io.grpc.Status;
// Stream tracer that adds detailed logging
public class LoggingStreamTracer extends ForwardingClientStreamTracer {
private final ClientStreamTracer delegate;
private final Logger logger;
public LoggingStreamTracer(ClientStreamTracer delegate) {
this.delegate = delegate;
this.logger = Logger.getLogger(LoggingStreamTracer.class.getName());
}
@Override
protected ClientStreamTracer delegate() {
return delegate;
}
@Override
public void streamCreated(Attributes transportAttrs, Metadata headers) {
logger.info("Stream created with " + headers.keys().size() + " headers");
super.streamCreated(transportAttrs, headers);
}
@Override
public void streamClosed(Status status) {
logger.info("Stream closed with status: " + status.getCode());
super.streamClosed(status);
}
@Override
public void outboundMessageSent(int seqNo, long optionalWireSize, long optionalUncompressedSize) {
logger.fine("Outbound message " + seqNo + ", wire size: " + optionalWireSize);
super.outboundMessageSent(seqNo, optionalWireSize, optionalUncompressedSize);
}
}
// Stream tracer that collects metrics
public class MetricsStreamTracer extends ForwardingClientStreamTracer {
private final ClientStreamTracer delegate;
private final StreamMetrics metrics;
private long startTime;
public MetricsStreamTracer(ClientStreamTracer delegate, StreamMetrics metrics) {
this.delegate = delegate;
this.metrics = metrics;
}
@Override
protected ClientStreamTracer delegate() {
return delegate;
}
@Override
public void streamCreated(Attributes transportAttrs, Metadata headers) {
startTime = System.nanoTime();
metrics.incrementStreamCount();
super.streamCreated(transportAttrs, headers);
}
@Override
public void streamClosed(Status status) {
long duration = System.nanoTime() - startTime;
metrics.recordStreamDuration(duration);
metrics.recordStreamStatus(status.getCode());
super.streamClosed(status);
}
}When creating custom forwarding implementations, follow these patterns:
delegate() methodsuper.methodName() to forward to the delegatetoString() for better debuggingForwarding classes typically preserve the error handling behavior of their delegates. Custom implementations should:
When creating forwarding implementations that hold resources:
Install with Tessl CLI
npx tessl i tessl/maven-io-grpc--grpc-util