or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

client-usage.mdconfiguration.mdexception-handling.mdindex.mdinterceptors.mdreactive-streaming.mdservice-implementation.md

exception-handling.mddocs/

0

# Exception Handling

1

2

Comprehensive exception handling system with customizable error transformation and gRPC status code mapping. The system provides hooks for converting application exceptions into proper gRPC status responses.

3

4

## Capabilities

5

6

### ExceptionHandler Abstract Class

7

8

Generic exception handler that intercepts exceptions during gRPC call processing and provides customization points for error transformation.

9

10

```java { .api }

11

/**

12

* Generic exception handler

13

*/

14

public abstract class ExceptionHandler<ReqT, RespT> extends

15

ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT> {

16

17

private final ServerCall<ReqT, RespT> call;

18

private final Metadata metadata;

19

20

public ExceptionHandler(ServerCall.Listener<ReqT> listener,

21

ServerCall<ReqT, RespT> call,

22

Metadata metadata) {

23

super(listener);

24

this.metadata = metadata;

25

this.call = call;

26

}

27

28

protected abstract void handleException(Throwable t,

29

ServerCall<ReqT, RespT> call,

30

Metadata metadata);

31

}

32

```

33

34

**Usage Example:**

35

36

```java

37

import io.quarkus.grpc.ExceptionHandler;

38

import io.grpc.Status;

39

import io.grpc.StatusRuntimeException;

40

41

public class CustomExceptionHandler<ReqT, RespT> extends ExceptionHandler<ReqT, RespT> {

42

43

public CustomExceptionHandler(ServerCall.Listener<ReqT> listener,

44

ServerCall<ReqT, RespT> call,

45

Metadata metadata) {

46

super(listener, call, metadata);

47

}

48

49

@Override

50

protected void handleException(Throwable t,

51

ServerCall<ReqT, RespT> call,

52

Metadata metadata) {

53

Status status;

54

55

if (t instanceof ValidationException) {

56

status = Status.INVALID_ARGUMENT.withDescription(t.getMessage());

57

} else if (t instanceof NotFoundException) {

58

status = Status.NOT_FOUND.withDescription(t.getMessage());

59

} else if (t instanceof UnauthorizedException) {

60

status = Status.UNAUTHENTICATED.withDescription(t.getMessage());

61

} else if (t instanceof ForbiddenException) {

62

status = Status.PERMISSION_DENIED.withDescription(t.getMessage());

63

} else {

64

status = Status.INTERNAL.withDescription("Internal server error");

65

}

66

67

call.close(status, metadata);

68

}

69

}

70

```

71

72

### ExceptionHandlerProvider Interface

73

74

Provider for creating ExceptionHandler instances. Implement this interface and expose it as a CDI bean to customize exception handling across all gRPC services.

75

76

```java { .api }

77

/**

78

* Provider for ExceptionHandler.

79

*

80

* To use a custom ExceptionHandler, extend {@link ExceptionHandler} and implement

81

* an {@link ExceptionHandlerProvider}, and expose it as a CDI bean.

82

*/

83

public interface ExceptionHandlerProvider {

84

<ReqT, RespT> ExceptionHandler<ReqT, RespT> createHandler(

85

Listener<ReqT> listener,

86

ServerCall<ReqT, RespT> serverCall,

87

Metadata metadata

88

);

89

90

default Throwable transform(Throwable t) {

91

return toStatusException(t, false); // previous default was false

92

}

93

94

/**

95

* Throw Status exception.

96

*

97

* @param t the throwable to transform

98

* @param runtime true if we should throw StatusRuntimeException, false for StatusException

99

* @return Status(Runtime)Exception

100

*/

101

static Exception toStatusException(Throwable t, boolean runtime);

102

103

/**

104

* Get Status from exception.

105

*

106

* @param t the throwable to read or create status from

107

* @return gRPC Status instance

108

*/

109

static Status toStatus(Throwable t);

110

111

/**

112

* Get optional Metadata from exception.

113

*

114

* @param t the throwable to read or create metadata from

115

* @return optional gRPC Metadata instance

116

*/

117

static Optional<Metadata> toTrailers(Throwable t);

118

}

119

```

120

121

**Usage Examples:**

122

123

```java

124

import io.quarkus.grpc.ExceptionHandlerProvider;

125

import io.quarkus.grpc.ExceptionHandler;

126

import jakarta.enterprise.context.ApplicationScoped;

127

128

@ApplicationScoped

129

public class CustomExceptionHandlerProvider implements ExceptionHandlerProvider {

130

131

@Override

132

public <ReqT, RespT> ExceptionHandler<ReqT, RespT> createHandler(

133

Listener<ReqT> listener,

134

ServerCall<ReqT, RespT> serverCall,

135

Metadata metadata) {

136

137

return new CustomExceptionHandler<>(listener, serverCall, metadata);

138

}

139

140

@Override

141

public Throwable transform(Throwable t) {

142

// Custom transformation logic

143

if (t instanceof BusinessException) {

144

BusinessException be = (BusinessException) t;

145

Status status = Status.INVALID_ARGUMENT

146

.withDescription(be.getMessage())

147

.withCause(be);

148

return status.asRuntimeException();

149

}

150

151

// Use default transformation for other exceptions

152

return ExceptionHandlerProvider.toStatusException(t, true);

153

}

154

}

155

```

156

157

### Status Utility Methods

158

159

The ExceptionHandlerProvider interface provides static utility methods for exception transformation:

160

161

```java { .api }

162

/**

163

* Throw Status exception.

164

*/

165

static Exception toStatusException(Throwable t, boolean runtime) {

166

// Converts any throwable to StatusException or StatusRuntimeException

167

}

168

169

/**

170

* Get Status from exception.

171

*/

172

static Status toStatus(Throwable t) {

173

// Extracts or creates gRPC Status from throwable

174

}

175

176

/**

177

* Get optional Metadata from exception.

178

*/

179

static Optional<Metadata> toTrailers(Throwable t) {

180

// Extracts optional Metadata trailers from exception

181

}

182

```

183

184

**Usage Examples:**

185

186

```java

187

import io.quarkus.grpc.ExceptionHandlerProvider;

188

import io.grpc.Status;

189

import io.grpc.Metadata;

190

191

public class ExceptionUtils {

192

193

public static void demonstrateStatusConversion() {

194

try {

195

riskyOperation();

196

} catch (Exception e) {

197

// Convert to gRPC status

198

Status status = ExceptionHandlerProvider.toStatus(e);

199

System.out.println("Status code: " + status.getCode());

200

System.out.println("Description: " + status.getDescription());

201

202

// Get trailers if available

203

Optional<Metadata> trailers = ExceptionHandlerProvider.toTrailers(e);

204

if (trailers.isPresent()) {

205

System.out.println("Has trailers: " + trailers.get().keys());

206

}

207

208

// Convert to StatusRuntimeException

209

Exception statusException = ExceptionHandlerProvider.toStatusException(e, true);

210

throw (StatusRuntimeException) statusException;

211

}

212

}

213

}

214

```

215

216

## Common Exception Handling Patterns

217

218

### Business Logic Exception Mapping

219

220

```java

221

@ApplicationScoped

222

public class BusinessExceptionHandler implements ExceptionHandlerProvider {

223

224

@Override

225

public <ReqT, RespT> ExceptionHandler<ReqT, RespT> createHandler(

226

Listener<ReqT> listener,

227

ServerCall<ReqT, RespT> serverCall,

228

Metadata metadata) {

229

230

return new ExceptionHandler<ReqT, RespT>(listener, serverCall, metadata) {

231

@Override

232

protected void handleException(Throwable t,

233

ServerCall<ReqT, RespT> call,

234

Metadata metadata) {

235

236

Status status = mapToGrpcStatus(t);

237

Metadata responseMetadata = createResponseMetadata(t, metadata);

238

239

call.close(status, responseMetadata);

240

}

241

};

242

}

243

244

private Status mapToGrpcStatus(Throwable t) {

245

if (t instanceof ValidationException) {

246

ValidationException ve = (ValidationException) t;

247

return Status.INVALID_ARGUMENT

248

.withDescription("Validation failed: " + ve.getMessage())

249

.withCause(ve);

250

}

251

252

if (t instanceof ResourceNotFoundException) {

253

return Status.NOT_FOUND

254

.withDescription("Resource not found: " + t.getMessage());

255

}

256

257

if (t instanceof InsufficientPermissionsException) {

258

return Status.PERMISSION_DENIED

259

.withDescription("Access denied: " + t.getMessage());

260

}

261

262

if (t instanceof RateLimitExceededException) {

263

return Status.RESOURCE_EXHAUSTED

264

.withDescription("Rate limit exceeded");

265

}

266

267

// Default for unexpected exceptions

268

return Status.INTERNAL

269

.withDescription("Internal server error")

270

.withCause(t);

271

}

272

273

private Metadata createResponseMetadata(Throwable t, Metadata requestMetadata) {

274

Metadata responseMetadata = new Metadata();

275

276

// Add correlation ID if present in request

277

Key<String> correlationKey = Key.of("correlation-id", Metadata.ASCII_STRING_MARSHALLER);

278

String correlationId = requestMetadata.get(correlationKey);

279

if (correlationId != null) {

280

responseMetadata.put(correlationKey, correlationId);

281

}

282

283

// Add error details for certain exception types

284

if (t instanceof ValidationException) {

285

Key<String> errorDetailsKey = Key.of("error-details", Metadata.ASCII_STRING_MARSHALLER);

286

ValidationException ve = (ValidationException) t;

287

responseMetadata.put(errorDetailsKey, ve.getValidationErrors().toString());

288

}

289

290

return responseMetadata;

291

}

292

}

293

```

294

295

### Structured Error Response

296

297

```java

298

public class StructuredErrorHandler implements ExceptionHandlerProvider {

299

300

@Override

301

public Throwable transform(Throwable t) {

302

ErrorInfo errorInfo = createErrorInfo(t);

303

304

Status status = Status.fromCode(errorInfo.getStatusCode())

305

.withDescription(errorInfo.getMessage());

306

307

Metadata metadata = new Metadata();

308

Key<String> errorCodeKey = Key.of("error-code", Metadata.ASCII_STRING_MARSHALLER);

309

metadata.put(errorCodeKey, errorInfo.getErrorCode());

310

311

if (errorInfo.hasDetails()) {

312

Key<String> detailsKey = Key.of("error-details", Metadata.ASCII_STRING_MARSHALLER);

313

metadata.put(detailsKey, errorInfo.getDetailsJson());

314

}

315

316

return status.asRuntimeException(metadata);

317

}

318

319

private ErrorInfo createErrorInfo(Throwable t) {

320

if (t instanceof ValidationException) {

321

ValidationException ve = (ValidationException) t;

322

return ErrorInfo.builder()

323

.statusCode(Status.Code.INVALID_ARGUMENT)

324

.errorCode("VALIDATION_FAILED")

325

.message(ve.getMessage())

326

.details(ve.getValidationErrors())

327

.build();

328

}

329

330

if (t instanceof ResourceNotFoundException) {

331

return ErrorInfo.builder()

332

.statusCode(Status.Code.NOT_FOUND)

333

.errorCode("RESOURCE_NOT_FOUND")

334

.message(t.getMessage())

335

.build();

336

}

337

338

// Default error info

339

return ErrorInfo.builder()

340

.statusCode(Status.Code.INTERNAL)

341

.errorCode("INTERNAL_ERROR")

342

.message("An unexpected error occurred")

343

.build();

344

}

345

}

346

```

347

348

### Reactive Exception Handling

349

350

In gRPC services that use Mutiny, exceptions can be handled reactively:

351

352

```java

353

@GrpcService

354

public class ReactiveServiceWithErrorHandling implements MutinyService {

355

356

public Uni<UserResponse> getUser(UserRequest request) {

357

return validateRequest(request)

358

.onItem().transformToUni(this::findUser)

359

.onFailure(ValidationException.class).transform(ex ->

360

new StatusRuntimeException(

361

Status.INVALID_ARGUMENT.withDescription(ex.getMessage())))

362

.onFailure(UserNotFoundException.class).transform(ex ->

363

new StatusRuntimeException(

364

Status.NOT_FOUND.withDescription("User not found")))

365

.onFailure().transform(ex -> {

366

if (ex instanceof StatusRuntimeException) {

367

return ex; // Already a gRPC exception

368

}

369

return new StatusRuntimeException(

370

Status.INTERNAL.withDescription("Unexpected error"));

371

});

372

}

373

374

private Uni<UserRequest> validateRequest(UserRequest request) {

375

if (request.getUserId().isEmpty()) {

376

return Uni.createFrom().failure(

377

new ValidationException("User ID is required"));

378

}

379

return Uni.createFrom().item(request);

380

}

381

}

382

```

383

384

## Exception Handling Best Practices

385

386

1. **Map business exceptions to appropriate gRPC status codes**

387

2. **Preserve error context in status descriptions**

388

3. **Use metadata for structured error information**

389

4. **Log exceptions appropriately (don't expose sensitive details)**

390

5. **Provide correlation IDs for error tracking**

391

6. **Use reactive error handling patterns in Mutiny services**

392

7. **Consider circuit breaker patterns for external service failures**