or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

forwarding-utilities.mdindex.mdload-balancing.mdserver-utilities.mdtls-management.md

server-utilities.mddocs/

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