CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-grpc--grpc-util

Advanced utilities for gRPC Java providing load balancing, TLS management, and server utilities

Pending
Overview
Eval results
Files

forwarding-utilities.mddocs/

Forwarding 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.

Capabilities

Forwarding Load Balancer

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);
        }
    }
}

Forwarding Load Balancer Helper

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));
    }
}

Forwarding Subchannel

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));
    }
}

Forwarding Client Stream Tracer

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);
    }
}

Common Patterns

Creating Custom Decorators

When creating custom forwarding implementations, follow these patterns:

  1. Always implement the abstract delegate() method
  2. Call super.methodName() to forward to the delegate
  3. Add custom logic before or after the forwarded call
  4. Handle exceptions appropriately
  5. Override toString() for better debugging

Error Handling

Forwarding classes typically preserve the error handling behavior of their delegates. Custom implementations should:

  • Catch and handle exceptions appropriately
  • Forward errors to delegates when appropriate
  • Log errors for debugging purposes
  • Not suppress critical errors

Resource Management

When creating forwarding implementations that hold resources:

  • Implement proper cleanup in shutdown methods
  • Forward shutdown calls to delegates
  • Close any additional resources opened by the forwarding implementation
  • Use try-with-resources or similar patterns for automatic cleanup

Install with Tessl CLI

npx tessl i tessl/maven-io-grpc--grpc-util

docs

forwarding-utilities.md

index.md

load-balancing.md

server-utilities.md

tls-management.md

tile.json