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

interceptors.mddocs/

0

# Interceptors

1

2

Interceptor system for implementing cross-cutting concerns like authentication, logging, and metrics across gRPC services and clients. Interceptors can be registered globally or for specific services/clients.

3

4

## Capabilities

5

6

### @GlobalInterceptor Annotation

7

8

Denotes an interceptor that should be registered for all gRPC services or all injected gRPC clients. The interceptor will be automatically applied without explicit registration.

9

10

```java { .api }

11

/**

12

* Denotes a {@link io.grpc.ServerInterceptor} that should be registered for all gRPC services, or a

13

* {@link io.grpc.ClientInterceptor} that should be registered for all injected gRPC clients.

14

*

15

* @see RegisterInterceptor

16

* @see RegisterClientInterceptor

17

*/

18

@Target({ FIELD, PARAMETER, TYPE, METHOD })

19

@Retention(RUNTIME)

20

public @interface GlobalInterceptor {

21

}

22

```

23

24

**Usage Examples:**

25

26

```java

27

import io.quarkus.grpc.GlobalInterceptor;

28

import io.grpc.ServerInterceptor;

29

import io.grpc.Metadata;

30

import io.grpc.ServerCall;

31

import io.grpc.ServerCallHandler;

32

import jakarta.enterprise.context.ApplicationScoped;

33

34

// Global server interceptor

35

@GlobalInterceptor

36

@ApplicationScoped

37

public class LoggingServerInterceptor implements ServerInterceptor {

38

39

@Override

40

public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(

41

ServerCall<ReqT, RespT> call,

42

Metadata headers,

43

ServerCallHandler<ReqT, RespT> next) {

44

45

String methodName = call.getMethodDescriptor().getFullMethodName();

46

System.out.println("Received call to: " + methodName);

47

48

return next.startCall(call, headers);

49

}

50

}

51

52

// Global client interceptor

53

@GlobalInterceptor

54

@ApplicationScoped

55

public class AuthClientInterceptor implements ClientInterceptor {

56

57

@Override

58

public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(

59

MethodDescriptor<ReqT, RespT> method,

60

CallOptions callOptions,

61

Channel next) {

62

63

return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(

64

next.newCall(method, callOptions)) {

65

66

@Override

67

public void start(Listener<RespT> responseListener, Metadata headers) {

68

// Add auth token to all outgoing calls

69

Key<String> authKey = Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);

70

headers.put(authKey, "Bearer " + getAuthToken());

71

super.start(responseListener, headers);

72

}

73

};

74

}

75

}

76

```

77

78

### @RegisterInterceptor Annotation

79

80

Registers a ServerInterceptor for a particular gRPC service. This annotation is repeatable, allowing multiple interceptors to be registered for the same service.

81

82

```java { .api }

83

/**

84

* Registers a {@link ServerInterceptor} for a particular gRPC service.

85

*

86

* @see GlobalInterceptor

87

*/

88

@Repeatable(RegisterInterceptors.class)

89

@Target(TYPE)

90

@Retention(RUNTIME)

91

public @interface RegisterInterceptor {

92

Class<? extends ServerInterceptor> value();

93

}

94

95

/**

96

* Container annotation for repeatable {@link RegisterInterceptor} annotations.

97

*/

98

@Target(TYPE)

99

@Retention(RUNTIME)

100

@Documented

101

public @interface RegisterInterceptors {

102

RegisterInterceptor[] value();

103

}

104

```

105

106

**Usage Examples:**

107

108

```java

109

import io.quarkus.grpc.RegisterInterceptor;

110

import io.quarkus.grpc.GrpcService;

111

112

// Single interceptor

113

@RegisterInterceptor(AuthInterceptor.class)

114

@GrpcService

115

public class SecureService implements MutinyService {

116

117

public Uni<SecureResponse> secureMethod(SecureRequest request) {

118

// This method will be intercepted by AuthInterceptor

119

return Uni.createFrom().item(SecureResponse.newBuilder().build());

120

}

121

}

122

123

// Multiple interceptors

124

@RegisterInterceptor(LoggingInterceptor.class)

125

@RegisterInterceptor(MetricsInterceptor.class)

126

@RegisterInterceptor(AuthInterceptor.class)

127

@GrpcService

128

public class FullyInterceptedService implements MutinyService {

129

130

public Uni<Response> method(Request request) {

131

// Interceptors will be applied in registration order

132

return Uni.createFrom().item(Response.newBuilder().build());

133

}

134

}

135

```

136

137

### @RegisterClientInterceptor Annotation

138

139

Registers a ClientInterceptor for an injected gRPC client. This annotation is repeatable and supports programmatic creation.

140

141

```java { .api }

142

/**

143

* Registers a {@link ClientInterceptor} for an injected gRPC client.

144

*

145

* @see GlobalInterceptor

146

*/

147

@Qualifier

148

@Repeatable(RegisterClientInterceptor.List.class)

149

@Target({ FIELD, PARAMETER })

150

@Retention(RUNTIME)

151

public @interface RegisterClientInterceptor {

152

153

Class<? extends ClientInterceptor> value();

154

155

@Target({ FIELD, PARAMETER })

156

@Retention(RUNTIME)

157

@interface List {

158

RegisterClientInterceptor[] value();

159

}

160

161

final class Literal extends AnnotationLiteral<RegisterClientInterceptor>

162

implements RegisterClientInterceptor {

163

private static final long serialVersionUID = 1L;

164

private final Class<? extends ClientInterceptor> value;

165

166

public static Literal of(Class<? extends ClientInterceptor> value);

167

168

public Class<? extends ClientInterceptor> value();

169

}

170

}

171

```

172

173

**Usage Examples:**

174

175

```java

176

import io.quarkus.grpc.RegisterClientInterceptor;

177

import io.quarkus.grpc.GrpcClient;

178

179

public class ClientWithInterceptors {

180

181

// Single client interceptor

182

@Inject

183

@GrpcClient("user-service")

184

@RegisterClientInterceptor(RetryInterceptor.class)

185

MutinyUserServiceGrpc userClient;

186

187

// Multiple client interceptors

188

@Inject

189

@GrpcClient("payment-service")

190

@RegisterClientInterceptor(AuthInterceptor.class)

191

@RegisterClientInterceptor(LoggingInterceptor.class)

192

MutinyPaymentServiceGrpc paymentClient;

193

194

// Programmatic interceptor registration

195

public void registerInterceptorProgrammatically() {

196

var literal = RegisterClientInterceptor.Literal.of(CustomInterceptor.class);

197

// Use literal in CDI lookup

198

}

199

}

200

```

201

202

## Common Interceptor Implementations

203

204

### Authentication Interceptor

205

206

```java

207

@ApplicationScoped

208

public class AuthInterceptor implements ServerInterceptor {

209

210

@Override

211

public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(

212

ServerCall<ReqT, RespT> call,

213

Metadata headers,

214

ServerCallHandler<ReqT, RespT> next) {

215

216

Key<String> authKey = Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);

217

String token = headers.get(authKey);

218

219

if (token == null || !isValidToken(token)) {

220

call.close(Status.UNAUTHENTICATED.withDescription("Invalid or missing auth token"),

221

new Metadata());

222

return new ServerCall.Listener<ReqT>() {};

223

}

224

225

return next.startCall(call, headers);

226

}

227

228

private boolean isValidToken(String token) {

229

return token.startsWith("Bearer ") && validateJwtToken(token.substring(7));

230

}

231

}

232

```

233

234

### Logging Interceptor

235

236

```java

237

@ApplicationScoped

238

public class LoggingInterceptor implements ServerInterceptor {

239

240

private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);

241

242

@Override

243

public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(

244

ServerCall<ReqT, RespT> call,

245

Metadata headers,

246

ServerCallHandler<ReqT, RespT> next) {

247

248

String methodName = call.getMethodDescriptor().getFullMethodName();

249

long startTime = System.currentTimeMillis();

250

251

logger.info("Starting gRPC call: {}", methodName);

252

253

return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(

254

next.startCall(call, headers)) {

255

256

@Override

257

public void onComplete() {

258

long duration = System.currentTimeMillis() - startTime;

259

logger.info("Completed gRPC call: {} in {}ms", methodName, duration);

260

super.onComplete();

261

}

262

263

@Override

264

public void onCancel() {

265

logger.warn("Cancelled gRPC call: {}", methodName);

266

super.onCancel();

267

}

268

};

269

}

270

}

271

```

272

273

### Metrics Interceptor

274

275

```java

276

@ApplicationScoped

277

public class MetricsInterceptor implements ServerInterceptor {

278

279

@Inject

280

MeterRegistry meterRegistry;

281

282

@Override

283

public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(

284

ServerCall<ReqT, RespT> call,

285

Metadata headers,

286

ServerCallHandler<ReqT, RespT> next) {

287

288

String methodName = call.getMethodDescriptor().getFullMethodName();

289

Timer.Sample sample = Timer.start(meterRegistry);

290

291

Counter.builder("grpc.server.requests")

292

.tag("method", methodName)

293

.register(meterRegistry)

294

.increment();

295

296

return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(

297

next.startCall(call, headers)) {

298

299

@Override

300

public void onComplete() {

301

sample.stop(Timer.builder("grpc.server.duration")

302

.tag("method", methodName)

303

.tag("status", "completed")

304

.register(meterRegistry));

305

super.onComplete();

306

}

307

308

@Override

309

public void onCancel() {

310

sample.stop(Timer.builder("grpc.server.duration")

311

.tag("method", methodName)

312

.tag("status", "cancelled")

313

.register(meterRegistry));

314

super.onCancel();

315

}

316

};

317

}

318

}

319

```

320

321

### Client Retry Interceptor

322

323

```java

324

@ApplicationScoped

325

public class RetryInterceptor implements ClientInterceptor {

326

327

private final int maxRetries = 3;

328

private final long retryDelayMs = 1000;

329

330

@Override

331

public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(

332

MethodDescriptor<ReqT, RespT> method,

333

CallOptions callOptions,

334

Channel next) {

335

336

return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(

337

next.newCall(method, callOptions)) {

338

339

@Override

340

public void start(Listener<RespT> responseListener, Metadata headers) {

341

super.start(new RetryingListener<>(responseListener, method, callOptions, next),

342

headers);

343

}

344

};

345

}

346

347

private class RetryingListener<RespT> extends

348

ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT> {

349

350

private int attemptCount = 0;

351

352

protected RetryingListener(Listener<RespT> delegate,

353

MethodDescriptor<?, RespT> method,

354

CallOptions callOptions,

355

Channel channel) {

356

super(delegate);

357

// Store for retry logic

358

}

359

360

@Override

361

public void onClose(Status status, Metadata trailers) {

362

if (status.isOk() || attemptCount >= maxRetries) {

363

super.onClose(status, trailers);

364

} else {

365

attemptCount++;

366

// Implement retry logic

367

scheduleRetry();

368

}

369

}

370

}

371

}

372

```

373

374

## Interceptor Ordering

375

376

- Global interceptors are applied first

377

- Service-specific interceptors (@RegisterInterceptor) are applied in registration order

378

- Client-specific interceptors (@RegisterClientInterceptor) are applied in registration order

379

- Interceptors form a chain where each interceptor can modify the request/response or short-circuit the call