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

server-utilities.mddocs/

Server Utilities

Utilities for server-side gRPC functionality including flexible service registries, status exception handling, and health checking integration.

Capabilities

Mutable Handler Registry

A thread-safe, mutable registry for server method handlers that allows dynamic service management.

/**
 * A registry that allows services to be added and removed dynamically.
 * Thread-safe for concurrent access during service registration and lookup.
 */
@ThreadSafe
public final class MutableHandlerRegistry extends HandlerRegistry {

  /**
   * Default constructor creates an empty registry
   */
  public MutableHandlerRegistry();

  /**
   * Adds a service to the registry, replacing any existing service with the same name
   * @param service the service definition to add
   * @return the previous service definition with the same name, or null if none existed
   */
  public ServerServiceDefinition addService(ServerServiceDefinition service);

  /**
   * Adds a bindable service to the registry, replacing any existing service with the same name
   * @param bindableService the bindable service to add
   * @return the previous service definition with the same name, or null if none existed
   */
  public ServerServiceDefinition addService(BindableService bindableService);

  /**
   * Removes a service from the registry
   * @param service the service definition to remove
   * @return true if the service was removed, false if it was not found
   */
  public boolean removeService(ServerServiceDefinition service);

  /**
   * Gets an immutable list of all registered services
   * @return list of all registered service definitions
   */
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2222")
  public List<ServerServiceDefinition> getServices();

  /**
   * Looks up a method by name and authority
   * @param methodName the full method name (e.g., "package.Service/Method")
   * @param authority the authority (may be null)
   * @return the method definition, or null if not found
   */
  @Override
  public ServerMethodDefinition<?, ?> lookupMethod(String methodName, @Nullable String authority);
}

Usage Examples:

import io.grpc.util.MutableHandlerRegistry;
import io.grpc.ServerServiceDefinition;
import io.grpc.BindableService;
import io.grpc.Server;
import io.grpc.ServerBuilder;

// Create mutable registry
MutableHandlerRegistry registry = new MutableHandlerRegistry();

// Add services dynamically
ServerServiceDefinition userService = UserServiceGrpc.bindService(new UserServiceImpl());
ServerServiceDefinition previousService = registry.addService(userService);

// Add bindable service directly
OrderServiceImpl orderService = new OrderServiceImpl();
registry.addService(orderService);

// Use registry with server
Server server = ServerBuilder.forPort(8080)
    .fallbackHandlerRegistry(registry)
    .build();

// Add services at runtime
PaymentServiceImpl paymentService = new PaymentServiceImpl();
registry.addService(paymentService);

// Remove services at runtime
registry.removeService(userService);

// Get all currently registered services
List<ServerServiceDefinition> allServices = registry.getServices();
System.out.println("Registered services: " + allServices.size());

// Lookup specific method
ServerMethodDefinition<?, ?> method = registry.lookupMethod("com.example.UserService/GetUser", null);
if (method != null) {
    System.out.println("Found method: " + method.getMethodDescriptor().getFullMethodName());
}

Dynamic Service Management:

// Service registry that manages lifecycle
public class DynamicServiceRegistry {
    private final MutableHandlerRegistry registry;
    private final Map<String, BindableService> activeServices;
    
    public DynamicServiceRegistry() {
        this.registry = new MutableHandlerRegistry();
        this.activeServices = new ConcurrentHashMap<>();
    }
    
    public void startService(String serviceName, BindableService service) {
        BindableService previous = activeServices.put(serviceName, service);
        if (previous != null) {
            // Stop previous service if needed
            stopService(previous);
        }
        registry.addService(service);
        System.out.println("Started service: " + serviceName);
    }
    
    public void stopService(String serviceName) {
        BindableService service = activeServices.remove(serviceName);
        if (service != null) {
            ServerServiceDefinition definition = service.bindService();
            registry.removeService(definition);
            System.out.println("Stopped service: " + serviceName);
        }
    }
    
    public MutableHandlerRegistry getRegistry() {
        return registry;
    }
}

Status Runtime Exception Interceptor

Server interceptor that transmits StatusRuntimeException details to clients for better error handling.

/**
 * Server interceptor that catches StatusRuntimeException thrown by service
 * implementations and transmits the status information to clients.
 */
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1824")
public final class TransmitStatusRuntimeExceptionInterceptor implements ServerInterceptor {

  /**
   * Gets the singleton instance of the interceptor
   * @return the interceptor instance
   */
  public static ServerInterceptor instance();

  /**
   * Intercepts server calls to handle StatusRuntimeException transmission
   * @param call the server call being intercepted
   * @param headers the request headers
   * @param next the next call handler in the chain
   * @return ServerCall.Listener for handling the call
   */
  @Override
  public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
      ServerCall<ReqT, RespT> call,
      Metadata headers,
      ServerCallHandler<ReqT, RespT> next);
}

Usage Examples:

import io.grpc.util.TransmitStatusRuntimeExceptionInterceptor;
import io.grpc.ServerInterceptors;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.StatusRuntimeException;
import io.grpc.Status;

// Add interceptor to server
Server server = ServerBuilder.forPort(8080)
    .addService(ServerInterceptors.intercept(
        new MyServiceImpl(),
        TransmitStatusRuntimeExceptionInterceptor.instance()
    ))
    .build();

// Service implementation that throws StatusRuntimeException
public class MyServiceImpl extends MyServiceGrpc.MyServiceImplBase {
    
    @Override
    public void getData(GetDataRequest request, StreamObserver<GetDataResponse> responseObserver) {
        try {
            // Business logic that might fail
            validateRequest(request);
            GetDataResponse response = processRequest(request);
            responseObserver.onNext(response);
            responseObserver.onCompleted();
        } catch (ValidationException e) {
            // Throw StatusRuntimeException - interceptor will transmit it properly
            throw Status.INVALID_ARGUMENT
                .withDescription("Invalid request: " + e.getMessage())
                .withCause(e)
                .asRuntimeException();
        } catch (DataNotFoundException e) {
            throw Status.NOT_FOUND
                .withDescription("Data not found: " + request.getId())
                .asRuntimeException();
        } catch (Exception e) {
            throw Status.INTERNAL
                .withDescription("Unexpected error")
                .withCause(e)
                .asRuntimeException();
        }
    }
}

// Global interceptor configuration
ServerBuilder<?> builder = ServerBuilder.forPort(8080)
    .intercept(TransmitStatusRuntimeExceptionInterceptor.instance());

// Add all services
builder.addService(new UserServiceImpl());
builder.addService(new OrderServiceImpl());
builder.addService(new PaymentServiceImpl());

Server server = builder.build();

Error Handling Patterns:

// Custom exception to status mapping
public class StatusExceptionHandler {
    
    public static StatusRuntimeException handleBusinessException(Exception e) {
        if (e instanceof ValidationException) {
            return Status.INVALID_ARGUMENT
                .withDescription(e.getMessage())
                .withCause(e)
                .asRuntimeException();
        } else if (e instanceof AuthenticationException) {
            return Status.UNAUTHENTICATED
                .withDescription("Authentication failed")
                .withCause(e)
                .asRuntimeException();
        } else if (e instanceof AuthorizationException) {
            return Status.PERMISSION_DENIED
                .withDescription("Access denied")
                .withCause(e)
                .asRuntimeException();
        } else if (e instanceof ResourceNotFoundException) {
            return Status.NOT_FOUND
                .withDescription(e.getMessage())
                .withCause(e)
                .asRuntimeException();
        } else if (e instanceof RateLimitException) {
            return Status.RESOURCE_EXHAUSTED
                .withDescription("Rate limit exceeded")
                .withCause(e)
                .asRuntimeException();
        } else {
            return Status.INTERNAL
                .withDescription("Internal server error")
                .withCause(e)
                .asRuntimeException();
        }
    }
}

// Service with structured error handling
public class OrderServiceImpl extends OrderServiceGrpc.OrderServiceImplBase {
    
    @Override
    public void createOrder(CreateOrderRequest request, StreamObserver<CreateOrderResponse> responseObserver) {
        try {
            Order order = orderProcessor.createOrder(request);
            CreateOrderResponse response = CreateOrderResponse.newBuilder()
                .setOrder(order)
                .build();
            responseObserver.onNext(response);
            responseObserver.onCompleted();
        } catch (Exception e) {
            throw StatusExceptionHandler.handleBusinessException(e);
        }
    }
}

Health Producer Helper

Internal helper for integrating health checking with load balancers.

/**
 * Helper that integrates health checking with load balancer subchannels.
 * Automatically wraps subchannels with health checking capabilities.
 */
@Internal
public final class HealthProducerHelper extends ForwardingLoadBalancerHelper {

  /**
   * Creates a new health producer helper
   * @param helper the underlying load balancer helper to wrap
   */
  public HealthProducerHelper(LoadBalancer.Helper helper);

  /**
   * Creates a subchannel with health checking integration
   * @param args arguments for subchannel creation
   * @return Subchannel with health checking capabilities
   */
  @Override
  public LoadBalancer.Subchannel createSubchannel(LoadBalancer.CreateSubchannelArgs args);

  /**
   * Gets the underlying helper
   * @return the delegate helper
   */
  @Override
  protected LoadBalancer.Helper delegate();
}

Usage Examples (Internal):

// Note: This is an internal API and should not be used directly in application code
// It's shown here for completeness and understanding of the gRPC util internals

import io.grpc.util.HealthProducerHelper;
import io.grpc.LoadBalancer;

// Internal usage within gRPC load balancers
public class CustomLoadBalancerWithHealth extends LoadBalancer {
    private final HealthProducerHelper healthHelper;
    
    public CustomLoadBalancerWithHealth(Helper helper) {
        this.healthHelper = new HealthProducerHelper(helper);
    }
    
    @Override
    public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
        // Create subchannels with automatic health checking
        CreateSubchannelArgs args = CreateSubchannelArgs.newBuilder()
            .setAddresses(resolvedAddresses.getAddresses())
            .build();
        
        Subchannel subchannel = healthHelper.createSubchannel(args);
        // The subchannel now has health checking integrated
        
        return Status.OK;
    }
}

Integration Patterns

Complete Server Setup

Combining multiple server utilities for a comprehensive server configuration:

import io.grpc.util.MutableHandlerRegistry;
import io.grpc.util.TransmitStatusRuntimeExceptionInterceptor;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.ServerInterceptors;

public class CompleteServerSetup {
    
    public static Server createServer(int port) {
        // Create mutable registry for dynamic service management
        MutableHandlerRegistry registry = new MutableHandlerRegistry();
        
        // Add initial services
        registry.addService(new UserServiceImpl());
        registry.addService(new OrderServiceImpl());
        
        // Build server with interceptors and registry
        return ServerBuilder.forPort(port)
            .intercept(TransmitStatusRuntimeExceptionInterceptor.instance())
            .fallbackHandlerRegistry(registry)
            .build();
    }
    
    public static void main(String[] args) throws Exception {
        Server server = createServer(8080);
        server.start();
        
        // Add services dynamically at runtime
        MutableHandlerRegistry registry = getRegistryFromServer(server);
        registry.addService(new PaymentServiceImpl());
        
        System.out.println("Server started on port 8080");
        server.awaitTermination();
    }
}

Service Lifecycle Management

Advanced service lifecycle management with graceful shutdown:

public class ServiceManager {
    private final MutableHandlerRegistry registry;
    private final Map<String, BindableService> services;
    private final ExecutorService executor;
    
    public ServiceManager() {
        this.registry = new MutableHandlerRegistry();
        this.services = new ConcurrentHashMap<>();
        this.executor = Executors.newCachedThreadPool();
    }
    
    public CompletableFuture<Void> startService(String name, BindableService service) {
        return CompletableFuture.runAsync(() -> {
            try {
                // Initialize service if needed
                if (service instanceof Initializable) {
                    ((Initializable) service).initialize();
                }
                
                // Add to registry
                services.put(name, service);
                registry.addService(service);
                
                System.out.println("Service started: " + name);
            } catch (Exception e) {
                throw new RuntimeException("Failed to start service: " + name, e);
            }
        }, executor);
    }
    
    public CompletableFuture<Void> stopService(String name) {
        return CompletableFuture.runAsync(() -> {
            BindableService service = services.remove(name);
            if (service != null) {
                try {
                    // Remove from registry
                    registry.removeService(service.bindService());
                    
                    // Shutdown service if needed
                    if (service instanceof AutoCloseable) {
                        ((AutoCloseable) service).close();
                    }
                    
                    System.out.println("Service stopped: " + name);
                } catch (Exception e) {
                    System.err.println("Error stopping service " + name + ": " + e.getMessage());
                }
            }
        }, executor);
    }
    
    public void shutdown() {
        // Stop all services
        services.keySet().forEach(name -> {
            try {
                stopService(name).get(5, TimeUnit.SECONDS);
            } catch (Exception e) {
                System.err.println("Error stopping service " + name + ": " + e.getMessage());
            }
        });
        
        executor.shutdown();
    }
    
    public MutableHandlerRegistry getRegistry() {
        return registry;
    }
}

Best Practices

Service Registration

  • Use descriptive service names for easier management and debugging
  • Check return values from addService() to handle existing services appropriately
  • Implement proper error handling when services fail to initialize
  • Use atomic operations when possible to maintain consistency

Error Handling

  • Always use StatusRuntimeException for gRPC-specific errors
  • Include meaningful descriptions in status messages
  • Preserve original exceptions as causes when appropriate
  • Map business exceptions to appropriate gRPC status codes

Resource Management

  • Clean up resources when removing services from the registry
  • Handle service lifecycle appropriately (initialization, shutdown)
  • Use thread-safe operations when accessing the registry concurrently
  • Monitor service health and remove failing services automatically

Security Considerations

  • Validate service configurations before adding to registry
  • Implement proper authentication/authorization in service implementations
  • Avoid exposing internal errors to clients unnecessarily
  • Log security-relevant events for auditing purposes

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