0
# Server Utilities
1
2
Utilities for server-side gRPC functionality including flexible service registries, status exception handling, and health checking integration.
3
4
## Capabilities
5
6
### Mutable Handler Registry
7
8
A thread-safe, mutable registry for server method handlers that allows dynamic service management.
9
10
```java { .api }
11
/**
12
* A registry that allows services to be added and removed dynamically.
13
* Thread-safe for concurrent access during service registration and lookup.
14
*/
15
@ThreadSafe
16
public final class MutableHandlerRegistry extends HandlerRegistry {
17
18
/**
19
* Default constructor creates an empty registry
20
*/
21
public MutableHandlerRegistry();
22
23
/**
24
* Adds a service to the registry, replacing any existing service with the same name
25
* @param service the service definition to add
26
* @return the previous service definition with the same name, or null if none existed
27
*/
28
public ServerServiceDefinition addService(ServerServiceDefinition service);
29
30
/**
31
* Adds a bindable service to the registry, replacing any existing service with the same name
32
* @param bindableService the bindable service to add
33
* @return the previous service definition with the same name, or null if none existed
34
*/
35
public ServerServiceDefinition addService(BindableService bindableService);
36
37
/**
38
* Removes a service from the registry
39
* @param service the service definition to remove
40
* @return true if the service was removed, false if it was not found
41
*/
42
public boolean removeService(ServerServiceDefinition service);
43
44
/**
45
* Gets an immutable list of all registered services
46
* @return list of all registered service definitions
47
*/
48
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2222")
49
public List<ServerServiceDefinition> getServices();
50
51
/**
52
* Looks up a method by name and authority
53
* @param methodName the full method name (e.g., "package.Service/Method")
54
* @param authority the authority (may be null)
55
* @return the method definition, or null if not found
56
*/
57
@Override
58
public ServerMethodDefinition<?, ?> lookupMethod(String methodName, @Nullable String authority);
59
}
60
```
61
62
**Usage Examples:**
63
64
```java
65
import io.grpc.util.MutableHandlerRegistry;
66
import io.grpc.ServerServiceDefinition;
67
import io.grpc.BindableService;
68
import io.grpc.Server;
69
import io.grpc.ServerBuilder;
70
71
// Create mutable registry
72
MutableHandlerRegistry registry = new MutableHandlerRegistry();
73
74
// Add services dynamically
75
ServerServiceDefinition userService = UserServiceGrpc.bindService(new UserServiceImpl());
76
ServerServiceDefinition previousService = registry.addService(userService);
77
78
// Add bindable service directly
79
OrderServiceImpl orderService = new OrderServiceImpl();
80
registry.addService(orderService);
81
82
// Use registry with server
83
Server server = ServerBuilder.forPort(8080)
84
.fallbackHandlerRegistry(registry)
85
.build();
86
87
// Add services at runtime
88
PaymentServiceImpl paymentService = new PaymentServiceImpl();
89
registry.addService(paymentService);
90
91
// Remove services at runtime
92
registry.removeService(userService);
93
94
// Get all currently registered services
95
List<ServerServiceDefinition> allServices = registry.getServices();
96
System.out.println("Registered services: " + allServices.size());
97
98
// Lookup specific method
99
ServerMethodDefinition<?, ?> method = registry.lookupMethod("com.example.UserService/GetUser", null);
100
if (method != null) {
101
System.out.println("Found method: " + method.getMethodDescriptor().getFullMethodName());
102
}
103
```
104
105
**Dynamic Service Management:**
106
107
```java
108
// Service registry that manages lifecycle
109
public class DynamicServiceRegistry {
110
private final MutableHandlerRegistry registry;
111
private final Map<String, BindableService> activeServices;
112
113
public DynamicServiceRegistry() {
114
this.registry = new MutableHandlerRegistry();
115
this.activeServices = new ConcurrentHashMap<>();
116
}
117
118
public void startService(String serviceName, BindableService service) {
119
BindableService previous = activeServices.put(serviceName, service);
120
if (previous != null) {
121
// Stop previous service if needed
122
stopService(previous);
123
}
124
registry.addService(service);
125
System.out.println("Started service: " + serviceName);
126
}
127
128
public void stopService(String serviceName) {
129
BindableService service = activeServices.remove(serviceName);
130
if (service != null) {
131
ServerServiceDefinition definition = service.bindService();
132
registry.removeService(definition);
133
System.out.println("Stopped service: " + serviceName);
134
}
135
}
136
137
public MutableHandlerRegistry getRegistry() {
138
return registry;
139
}
140
}
141
```
142
143
### Status Runtime Exception Interceptor
144
145
Server interceptor that transmits `StatusRuntimeException` details to clients for better error handling.
146
147
```java { .api }
148
/**
149
* Server interceptor that catches StatusRuntimeException thrown by service
150
* implementations and transmits the status information to clients.
151
*/
152
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1824")
153
public final class TransmitStatusRuntimeExceptionInterceptor implements ServerInterceptor {
154
155
/**
156
* Gets the singleton instance of the interceptor
157
* @return the interceptor instance
158
*/
159
public static ServerInterceptor instance();
160
161
/**
162
* Intercepts server calls to handle StatusRuntimeException transmission
163
* @param call the server call being intercepted
164
* @param headers the request headers
165
* @param next the next call handler in the chain
166
* @return ServerCall.Listener for handling the call
167
*/
168
@Override
169
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
170
ServerCall<ReqT, RespT> call,
171
Metadata headers,
172
ServerCallHandler<ReqT, RespT> next);
173
}
174
```
175
176
**Usage Examples:**
177
178
```java
179
import io.grpc.util.TransmitStatusRuntimeExceptionInterceptor;
180
import io.grpc.ServerInterceptors;
181
import io.grpc.Server;
182
import io.grpc.ServerBuilder;
183
import io.grpc.StatusRuntimeException;
184
import io.grpc.Status;
185
186
// Add interceptor to server
187
Server server = ServerBuilder.forPort(8080)
188
.addService(ServerInterceptors.intercept(
189
new MyServiceImpl(),
190
TransmitStatusRuntimeExceptionInterceptor.instance()
191
))
192
.build();
193
194
// Service implementation that throws StatusRuntimeException
195
public class MyServiceImpl extends MyServiceGrpc.MyServiceImplBase {
196
197
@Override
198
public void getData(GetDataRequest request, StreamObserver<GetDataResponse> responseObserver) {
199
try {
200
// Business logic that might fail
201
validateRequest(request);
202
GetDataResponse response = processRequest(request);
203
responseObserver.onNext(response);
204
responseObserver.onCompleted();
205
} catch (ValidationException e) {
206
// Throw StatusRuntimeException - interceptor will transmit it properly
207
throw Status.INVALID_ARGUMENT
208
.withDescription("Invalid request: " + e.getMessage())
209
.withCause(e)
210
.asRuntimeException();
211
} catch (DataNotFoundException e) {
212
throw Status.NOT_FOUND
213
.withDescription("Data not found: " + request.getId())
214
.asRuntimeException();
215
} catch (Exception e) {
216
throw Status.INTERNAL
217
.withDescription("Unexpected error")
218
.withCause(e)
219
.asRuntimeException();
220
}
221
}
222
}
223
224
// Global interceptor configuration
225
ServerBuilder<?> builder = ServerBuilder.forPort(8080)
226
.intercept(TransmitStatusRuntimeExceptionInterceptor.instance());
227
228
// Add all services
229
builder.addService(new UserServiceImpl());
230
builder.addService(new OrderServiceImpl());
231
builder.addService(new PaymentServiceImpl());
232
233
Server server = builder.build();
234
```
235
236
**Error Handling Patterns:**
237
238
```java
239
// Custom exception to status mapping
240
public class StatusExceptionHandler {
241
242
public static StatusRuntimeException handleBusinessException(Exception e) {
243
if (e instanceof ValidationException) {
244
return Status.INVALID_ARGUMENT
245
.withDescription(e.getMessage())
246
.withCause(e)
247
.asRuntimeException();
248
} else if (e instanceof AuthenticationException) {
249
return Status.UNAUTHENTICATED
250
.withDescription("Authentication failed")
251
.withCause(e)
252
.asRuntimeException();
253
} else if (e instanceof AuthorizationException) {
254
return Status.PERMISSION_DENIED
255
.withDescription("Access denied")
256
.withCause(e)
257
.asRuntimeException();
258
} else if (e instanceof ResourceNotFoundException) {
259
return Status.NOT_FOUND
260
.withDescription(e.getMessage())
261
.withCause(e)
262
.asRuntimeException();
263
} else if (e instanceof RateLimitException) {
264
return Status.RESOURCE_EXHAUSTED
265
.withDescription("Rate limit exceeded")
266
.withCause(e)
267
.asRuntimeException();
268
} else {
269
return Status.INTERNAL
270
.withDescription("Internal server error")
271
.withCause(e)
272
.asRuntimeException();
273
}
274
}
275
}
276
277
// Service with structured error handling
278
public class OrderServiceImpl extends OrderServiceGrpc.OrderServiceImplBase {
279
280
@Override
281
public void createOrder(CreateOrderRequest request, StreamObserver<CreateOrderResponse> responseObserver) {
282
try {
283
Order order = orderProcessor.createOrder(request);
284
CreateOrderResponse response = CreateOrderResponse.newBuilder()
285
.setOrder(order)
286
.build();
287
responseObserver.onNext(response);
288
responseObserver.onCompleted();
289
} catch (Exception e) {
290
throw StatusExceptionHandler.handleBusinessException(e);
291
}
292
}
293
}
294
```
295
296
### Health Producer Helper
297
298
Internal helper for integrating health checking with load balancers.
299
300
```java { .api }
301
/**
302
* Helper that integrates health checking with load balancer subchannels.
303
* Automatically wraps subchannels with health checking capabilities.
304
*/
305
@Internal
306
public final class HealthProducerHelper extends ForwardingLoadBalancerHelper {
307
308
/**
309
* Creates a new health producer helper
310
* @param helper the underlying load balancer helper to wrap
311
*/
312
public HealthProducerHelper(LoadBalancer.Helper helper);
313
314
/**
315
* Creates a subchannel with health checking integration
316
* @param args arguments for subchannel creation
317
* @return Subchannel with health checking capabilities
318
*/
319
@Override
320
public LoadBalancer.Subchannel createSubchannel(LoadBalancer.CreateSubchannelArgs args);
321
322
/**
323
* Gets the underlying helper
324
* @return the delegate helper
325
*/
326
@Override
327
protected LoadBalancer.Helper delegate();
328
}
329
```
330
331
**Usage Examples (Internal):**
332
333
```java
334
// Note: This is an internal API and should not be used directly in application code
335
// It's shown here for completeness and understanding of the gRPC util internals
336
337
import io.grpc.util.HealthProducerHelper;
338
import io.grpc.LoadBalancer;
339
340
// Internal usage within gRPC load balancers
341
public class CustomLoadBalancerWithHealth extends LoadBalancer {
342
private final HealthProducerHelper healthHelper;
343
344
public CustomLoadBalancerWithHealth(Helper helper) {
345
this.healthHelper = new HealthProducerHelper(helper);
346
}
347
348
@Override
349
public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
350
// Create subchannels with automatic health checking
351
CreateSubchannelArgs args = CreateSubchannelArgs.newBuilder()
352
.setAddresses(resolvedAddresses.getAddresses())
353
.build();
354
355
Subchannel subchannel = healthHelper.createSubchannel(args);
356
// The subchannel now has health checking integrated
357
358
return Status.OK;
359
}
360
}
361
```
362
363
## Integration Patterns
364
365
### Complete Server Setup
366
367
Combining multiple server utilities for a comprehensive server configuration:
368
369
```java
370
import io.grpc.util.MutableHandlerRegistry;
371
import io.grpc.util.TransmitStatusRuntimeExceptionInterceptor;
372
import io.grpc.Server;
373
import io.grpc.ServerBuilder;
374
import io.grpc.ServerInterceptors;
375
376
public class CompleteServerSetup {
377
378
public static Server createServer(int port) {
379
// Create mutable registry for dynamic service management
380
MutableHandlerRegistry registry = new MutableHandlerRegistry();
381
382
// Add initial services
383
registry.addService(new UserServiceImpl());
384
registry.addService(new OrderServiceImpl());
385
386
// Build server with interceptors and registry
387
return ServerBuilder.forPort(port)
388
.intercept(TransmitStatusRuntimeExceptionInterceptor.instance())
389
.fallbackHandlerRegistry(registry)
390
.build();
391
}
392
393
public static void main(String[] args) throws Exception {
394
Server server = createServer(8080);
395
server.start();
396
397
// Add services dynamically at runtime
398
MutableHandlerRegistry registry = getRegistryFromServer(server);
399
registry.addService(new PaymentServiceImpl());
400
401
System.out.println("Server started on port 8080");
402
server.awaitTermination();
403
}
404
}
405
```
406
407
### Service Lifecycle Management
408
409
Advanced service lifecycle management with graceful shutdown:
410
411
```java
412
public class ServiceManager {
413
private final MutableHandlerRegistry registry;
414
private final Map<String, BindableService> services;
415
private final ExecutorService executor;
416
417
public ServiceManager() {
418
this.registry = new MutableHandlerRegistry();
419
this.services = new ConcurrentHashMap<>();
420
this.executor = Executors.newCachedThreadPool();
421
}
422
423
public CompletableFuture<Void> startService(String name, BindableService service) {
424
return CompletableFuture.runAsync(() -> {
425
try {
426
// Initialize service if needed
427
if (service instanceof Initializable) {
428
((Initializable) service).initialize();
429
}
430
431
// Add to registry
432
services.put(name, service);
433
registry.addService(service);
434
435
System.out.println("Service started: " + name);
436
} catch (Exception e) {
437
throw new RuntimeException("Failed to start service: " + name, e);
438
}
439
}, executor);
440
}
441
442
public CompletableFuture<Void> stopService(String name) {
443
return CompletableFuture.runAsync(() -> {
444
BindableService service = services.remove(name);
445
if (service != null) {
446
try {
447
// Remove from registry
448
registry.removeService(service.bindService());
449
450
// Shutdown service if needed
451
if (service instanceof AutoCloseable) {
452
((AutoCloseable) service).close();
453
}
454
455
System.out.println("Service stopped: " + name);
456
} catch (Exception e) {
457
System.err.println("Error stopping service " + name + ": " + e.getMessage());
458
}
459
}
460
}, executor);
461
}
462
463
public void shutdown() {
464
// Stop all services
465
services.keySet().forEach(name -> {
466
try {
467
stopService(name).get(5, TimeUnit.SECONDS);
468
} catch (Exception e) {
469
System.err.println("Error stopping service " + name + ": " + e.getMessage());
470
}
471
});
472
473
executor.shutdown();
474
}
475
476
public MutableHandlerRegistry getRegistry() {
477
return registry;
478
}
479
}
480
```
481
482
## Best Practices
483
484
### Service Registration
485
486
- **Use descriptive service names** for easier management and debugging
487
- **Check return values** from `addService()` to handle existing services appropriately
488
- **Implement proper error handling** when services fail to initialize
489
- **Use atomic operations** when possible to maintain consistency
490
491
### Error Handling
492
493
- **Always use StatusRuntimeException** for gRPC-specific errors
494
- **Include meaningful descriptions** in status messages
495
- **Preserve original exceptions** as causes when appropriate
496
- **Map business exceptions** to appropriate gRPC status codes
497
498
### Resource Management
499
500
- **Clean up resources** when removing services from the registry
501
- **Handle service lifecycle** appropriately (initialization, shutdown)
502
- **Use thread-safe operations** when accessing the registry concurrently
503
- **Monitor service health** and remove failing services automatically
504
505
### Security Considerations
506
507
- **Validate service configurations** before adding to registry
508
- **Implement proper authentication/authorization** in service implementations
509
- **Avoid exposing internal errors** to clients unnecessarily
510
- **Log security-relevant events** for auditing purposes