Advanced utilities for gRPC Java providing load balancing, TLS management, and server utilities
—
Utilities for server-side gRPC functionality including flexible service registries, status exception handling, and health checking integration.
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;
}
}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);
}
}
}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;
}
}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();
}
}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;
}
}addService() to handle existing services appropriatelyInstall with Tessl CLI
npx tessl i tessl/maven-io-grpc--grpc-util