0
# Asynchronous Processing
1
2
Server-side asynchronous request processing including async contexts, chunked output, broadcasting, and managed async execution. Enables scalable handling of long-running operations, streaming responses, and real-time communication patterns.
3
4
## Capabilities
5
6
### AsyncContext
7
8
Server-side asynchronous request processing context providing access to both request and response for asynchronous operations.
9
10
```java { .api }
11
/**
12
* Server-side asynchronous processing context extending AsyncResponse.
13
* Provides access to request and response contexts for asynchronous operations.
14
*/
15
public interface AsyncContext extends AsyncResponse {
16
17
/**
18
* Get the container request associated with this async context.
19
* @return ContainerRequest for the asynchronous operation
20
*/
21
ContainerRequest getContainerRequest();
22
23
/**
24
* Get the container response associated with this async context.
25
* @return ContainerResponse for the asynchronous operation
26
*/
27
ContainerResponse getContainerResponse();
28
29
/**
30
* Suspend the request processing.
31
* @return true if successfully suspended, false if already suspended
32
*/
33
boolean suspend();
34
35
/**
36
* Suspend request processing with timeout.
37
* @param time Timeout value
38
* @param unit Time unit for timeout
39
* @return true if successfully suspended
40
*/
41
boolean suspend(long time, TimeUnit unit);
42
43
/**
44
* Resume processing with a response.
45
* @param response Response to resume with
46
* @return true if successfully resumed
47
*/
48
boolean resume(Object response);
49
50
/**
51
* Resume processing with an exception.
52
* @param throwable Exception to resume with
53
* @return true if successfully resumed
54
*/
55
boolean resume(Throwable throwable);
56
57
/**
58
* Cancel the asynchronous processing.
59
* @return true if successfully cancelled
60
*/
61
boolean cancel();
62
63
/**
64
* Check if the async processing is suspended.
65
* @return true if currently suspended
66
*/
67
boolean isSuspended();
68
69
/**
70
* Check if the async processing is cancelled.
71
* @return true if cancelled
72
*/
73
boolean isCancelled();
74
}
75
```
76
77
**Usage Examples:**
78
79
```java
80
import org.glassfish.jersey.server.AsyncContext;
81
import org.glassfish.jersey.server.ContainerRequest;
82
import jakarta.ws.rs.container.AsyncResponse;
83
import jakarta.ws.rs.container.Suspended;
84
import java.util.concurrent.CompletableFuture;
85
import java.util.concurrent.TimeUnit;
86
87
@Path("/async")
88
public class AsyncResource {
89
90
@GET
91
@Path("/data")
92
public void getDataAsync(@Suspended AsyncResponse asyncResponse) {
93
// Cast to AsyncContext for additional functionality
94
AsyncContext asyncContext = (AsyncContext) asyncResponse;
95
96
// Access request information
97
ContainerRequest request = asyncContext.getContainerRequest();
98
String userAgent = request.getHeaderString("User-Agent");
99
100
// Suspend with timeout
101
asyncContext.suspend(30, TimeUnit.SECONDS);
102
103
// Perform async operation
104
CompletableFuture.supplyAsync(() -> {
105
return performLongRunningOperation();
106
}).thenAccept(result -> {
107
// Resume with result
108
asyncContext.resume(result);
109
}).exceptionally(throwable -> {
110
// Resume with exception
111
asyncContext.resume(throwable);
112
return null;
113
});
114
}
115
116
@POST
117
@Path("/cancel")
118
public String cancelAsync(@QueryParam("id") String operationId) {
119
AsyncContext context = findAsyncContext(operationId);
120
if (context != null && context.isSuspended()) {
121
boolean cancelled = context.cancel();
122
return cancelled ? "Cancelled" : "Could not cancel";
123
}
124
return "Operation not found or not suspended";
125
}
126
}
127
```
128
129
### ChunkedOutput
130
131
Support for chunked transfer encoding responses enabling streaming of data to clients in chunks.
132
133
```java { .api }
134
/**
135
* Chunked output for streaming responses to clients.
136
* Extends GenericType and implements Closeable for resource management.
137
*/
138
public class ChunkedOutput<T> extends GenericType<T> implements Closeable {
139
140
/**
141
* Create chunked output for specified chunk type.
142
* @param chunkType Type of chunks to be written
143
*/
144
public ChunkedOutput(Type chunkType);
145
146
/**
147
* Create chunked output for specified chunk type with separator.
148
* @param chunkType Type of chunks to be written
149
* @param separator Separator between chunks
150
*/
151
public ChunkedOutput(Type chunkType, String separator);
152
153
/**
154
* Check if the chunked output is closed.
155
* @return true if closed, false otherwise
156
*/
157
public boolean isClosed();
158
159
/**
160
* Write a chunk to the output.
161
* @param chunk Chunk data to write
162
* @throws IOException if writing fails or output is closed
163
*/
164
public void write(T chunk) throws IOException;
165
166
/**
167
* Close the chunked output stream.
168
* @throws IOException if closing fails
169
*/
170
public void close() throws IOException;
171
172
/**
173
* Set exception callback for error handling.
174
* @param callback Callback to handle exceptions
175
*/
176
public void setExceptionCallback(ChunkedOutput.ExceptionCallback callback);
177
178
/**
179
* Exception callback interface for handling write errors.
180
*/
181
public interface ExceptionCallback {
182
void onException(ChunkedOutput chunkedOutput, Exception exception);
183
}
184
}
185
```
186
187
**Usage Examples:**
188
189
```java
190
import org.glassfish.jersey.server.ChunkedOutput;
191
import jakarta.ws.rs.GET;
192
import jakarta.ws.rs.Path;
193
import jakarta.ws.rs.Produces;
194
import jakarta.ws.rs.core.MediaType;
195
import java.io.IOException;
196
import java.util.concurrent.Executors;
197
import java.util.concurrent.ScheduledExecutorService;
198
import java.util.concurrent.TimeUnit;
199
200
@Path("/stream")
201
public class StreamingResource {
202
203
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);
204
205
@GET
206
@Path("/data")
207
@Produces(MediaType.APPLICATION_JSON)
208
public ChunkedOutput<String> streamData() {
209
ChunkedOutput<String> output = new ChunkedOutput<>(String.class);
210
211
// Set exception callback
212
output.setExceptionCallback((chunkedOutput, exception) -> {
213
System.err.println("Streaming error: " + exception.getMessage());
214
});
215
216
// Stream data every second for 10 seconds
217
executor.scheduleAtFixedRate(() -> {
218
try {
219
if (!output.isClosed()) {
220
String data = generateData();
221
output.write(data);
222
}
223
} catch (IOException e) {
224
try {
225
output.close();
226
} catch (IOException closeEx) {
227
// Log close exception
228
}
229
}
230
}, 0, 1, TimeUnit.SECONDS);
231
232
// Auto-close after 10 seconds
233
executor.schedule(() -> {
234
try {
235
output.close();
236
} catch (IOException e) {
237
// Log exception
238
}
239
}, 10, TimeUnit.SECONDS);
240
241
return output;
242
}
243
244
@GET
245
@Path("/logs")
246
@Produces(MediaType.TEXT_PLAIN)
247
public ChunkedOutput<String> streamLogs() {
248
ChunkedOutput<String> output = new ChunkedOutput<>(String.class, "\n");
249
250
// Stream log entries
251
streamLogEntries(output);
252
253
return output;
254
}
255
256
private String generateData() {
257
return "{\"timestamp\":" + System.currentTimeMillis() + ",\"data\":\"example\"}";
258
}
259
}
260
```
261
262
### Broadcaster
263
264
Broadcasting utility for sending messages to multiple clients simultaneously, useful for real-time notifications and pub-sub patterns.
265
266
```java { .api }
267
/**
268
* Broadcaster for sending messages to multiple ChunkedOutput instances.
269
* Implements BroadcasterListener for lifecycle management.
270
*/
271
public final class Broadcaster<T> implements BroadcasterListener<T> {
272
273
/**
274
* Create a broadcaster for specific chunk type without automatic closing.
275
* @param chunkType Type of chunks to broadcast
276
* @return Broadcaster instance
277
*/
278
public static <T> Broadcaster<T> createOnly(Class<T> chunkType);
279
280
/**
281
* Create a broadcaster for specific chunk type with automatic closing.
282
* @param chunkType Type of chunks to broadcast
283
* @return Broadcaster instance that auto-closes when empty
284
*/
285
public static <T> Broadcaster<T> create(Class<T> chunkType);
286
287
/**
288
* Add a chunked output to the broadcaster.
289
* @param chunkedOutput ChunkedOutput to add
290
* @return true if successfully added
291
*/
292
public boolean add(ChunkedOutput<T> chunkedOutput);
293
294
/**
295
* Remove a chunked output from the broadcaster.
296
* @param chunkedOutput ChunkedOutput to remove
297
* @return true if successfully removed
298
*/
299
public boolean remove(ChunkedOutput<T> chunkedOutput);
300
301
/**
302
* Broadcast a chunk to all registered outputs.
303
* @param chunk Chunk to broadcast
304
*/
305
public void broadcast(T chunk);
306
307
/**
308
* Close all registered outputs and clear the broadcaster.
309
*/
310
public void closeAll();
311
312
/**
313
* Get the number of registered outputs.
314
* @return Number of active chunked outputs
315
*/
316
public int size();
317
318
/**
319
* Check if the broadcaster is empty.
320
* @return true if no outputs are registered
321
*/
322
public boolean isEmpty();
323
324
/**
325
* Add a broadcaster listener.
326
* @param listener Listener to add
327
*/
328
public void addListener(BroadcasterListener<T> listener);
329
330
/**
331
* Remove a broadcaster listener.
332
* @param listener Listener to remove
333
*/
334
public void removeListener(BroadcasterListener<T> listener);
335
}
336
```
337
338
**Usage Examples:**
339
340
```java
341
import org.glassfish.jersey.server.Broadcaster;
342
import org.glassfish.jersey.server.ChunkedOutput;
343
import org.glassfish.jersey.server.BroadcasterListener;
344
import jakarta.ws.rs.GET;
345
import jakarta.ws.rs.POST;
346
import jakarta.ws.rs.Path;
347
import jakarta.ws.rs.Produces;
348
import jakarta.ws.rs.core.MediaType;
349
350
@Path("/notifications")
351
public class NotificationResource {
352
353
// Static broadcaster for notifications
354
private static final Broadcaster<String> broadcaster = Broadcaster.create(String.class);
355
356
static {
357
// Add listener for broadcaster events
358
broadcaster.addListener(new BroadcasterListener<String>() {
359
@Override
360
public void onClose(ChunkedOutput<String> chunkedOutput) {
361
System.out.println("Client disconnected from notifications");
362
}
363
364
@Override
365
public void onException(ChunkedOutput<String> chunkedOutput, Exception exception) {
366
System.err.println("Broadcast error: " + exception.getMessage());
367
}
368
});
369
}
370
371
@GET
372
@Path("/subscribe")
373
@Produces(MediaType.TEXT_PLAIN)
374
public ChunkedOutput<String> subscribe() {
375
ChunkedOutput<String> output = new ChunkedOutput<>(String.class);
376
377
// Add to broadcaster
378
broadcaster.add(output);
379
380
// Send welcome message
381
try {
382
output.write("Connected to notifications");
383
} catch (IOException e) {
384
broadcaster.remove(output);
385
}
386
387
return output;
388
}
389
390
@POST
391
@Path("/send")
392
public String sendNotification(String message) {
393
// Broadcast to all subscribers
394
broadcaster.broadcast("NOTIFICATION: " + message);
395
396
return "Notification sent to " + broadcaster.size() + " subscribers";
397
}
398
399
@GET
400
@Path("/stats")
401
@Produces(MediaType.APPLICATION_JSON)
402
public String getStats() {
403
return "{\"subscribers\":" + broadcaster.size() + ",\"isEmpty\":" + broadcaster.isEmpty() + "}";
404
}
405
406
@POST
407
@Path("/shutdown")
408
public String shutdown() {
409
broadcaster.closeAll();
410
return "All notification subscribers disconnected";
411
}
412
}
413
```
414
415
### BroadcasterListener
416
417
Event listener interface for broadcaster lifecycle events and error handling.
418
419
```java { .api }
420
/**
421
* Listener interface for broadcaster events.
422
* Provides callbacks for output lifecycle and error handling.
423
*/
424
public interface BroadcasterListener<T> {
425
426
/**
427
* Called when a chunked output is closed.
428
* @param chunkedOutput ChunkedOutput that was closed
429
*/
430
default void onClose(ChunkedOutput<T> chunkedOutput) {
431
// Default implementation does nothing
432
}
433
434
/**
435
* Called when an exception occurs during broadcasting.
436
* @param chunkedOutput ChunkedOutput where exception occurred
437
* @param exception Exception that occurred
438
*/
439
default void onException(ChunkedOutput<T> chunkedOutput, Exception exception) {
440
// Default implementation does nothing
441
}
442
}
443
```
444
445
### Managed Async Annotations
446
447
Annotations for controlling managed asynchronous execution in Jersey resources.
448
449
```java { .api }
450
/**
451
* Annotation marking methods for managed asynchronous execution.
452
* Methods annotated with this will be executed on a managed thread pool.
453
*/
454
@Target({ElementType.METHOD, ElementType.TYPE})
455
@Retention(RetentionPolicy.RUNTIME)
456
public @interface ManagedAsync {
457
// Marker annotation - no parameters
458
}
459
460
/**
461
* Qualifier annotation for injecting managed async executor.
462
* Used with @Inject to get the managed executor service.
463
*/
464
@Qualifier
465
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
466
@Retention(RetentionPolicy.RUNTIME)
467
public @interface ManagedAsyncExecutor {
468
// Qualifier annotation - no parameters
469
}
470
471
/**
472
* Qualifier annotation for injecting background scheduler.
473
* Used with @Inject to get the background scheduled executor service.
474
*/
475
@Qualifier
476
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
477
@Retention(RetentionPolicy.RUNTIME)
478
public @interface BackgroundScheduler {
479
// Qualifier annotation - no parameters
480
}
481
```
482
483
**Usage Examples:**
484
485
```java
486
import org.glassfish.jersey.server.ManagedAsync;
487
import org.glassfish.jersey.server.ManagedAsyncExecutor;
488
import org.glassfish.jersey.server.BackgroundScheduler;
489
import jakarta.inject.Inject;
490
import jakarta.ws.rs.GET;
491
import jakarta.ws.rs.Path;
492
import jakarta.ws.rs.container.AsyncResponse;
493
import jakarta.ws.rs.container.Suspended;
494
import java.util.concurrent.ScheduledExecutorService;
495
import java.util.concurrent.ExecutorService;
496
import java.util.concurrent.TimeUnit;
497
498
@Path("/managed")
499
public class ManagedAsyncResource {
500
501
@Inject
502
@ManagedAsyncExecutor
503
private ExecutorService asyncExecutor;
504
505
@Inject
506
@BackgroundScheduler
507
private ScheduledExecutorService scheduler;
508
509
@GET
510
@Path("/simple")
511
@ManagedAsync
512
public String getManagedAsync() {
513
// This method will be executed on managed thread pool
514
return performLongOperation();
515
}
516
517
@GET
518
@Path("/custom")
519
public void getCustomAsync(@Suspended AsyncResponse asyncResponse) {
520
// Use injected managed executor
521
asyncExecutor.submit(() -> {
522
try {
523
String result = performLongOperation();
524
asyncResponse.resume(result);
525
} catch (Exception e) {
526
asyncResponse.resume(e);
527
}
528
});
529
}
530
531
@GET
532
@Path("/scheduled")
533
public void getScheduledAsync(@Suspended AsyncResponse asyncResponse) {
534
// Use injected background scheduler
535
scheduler.schedule(() -> {
536
try {
537
String result = performDelayedOperation();
538
asyncResponse.resume(result);
539
} catch (Exception e) {
540
asyncResponse.resume(e);
541
}
542
}, 5, TimeUnit.SECONDS);
543
}
544
545
@ManagedAsync
546
@GET
547
@Path("/streaming")
548
public ChunkedOutput<String> getManagedStreaming() {
549
// Managed async with streaming
550
ChunkedOutput<String> output = new ChunkedOutput<>(String.class);
551
552
scheduler.scheduleAtFixedRate(() -> {
553
try {
554
if (!output.isClosed()) {
555
output.write("Managed stream data: " + System.currentTimeMillis());
556
}
557
} catch (IOException e) {
558
try {
559
output.close();
560
} catch (IOException closeEx) {
561
// Log close exception
562
}
563
}
564
}, 0, 2, TimeUnit.SECONDS);
565
566
return output;
567
}
568
569
private String performLongOperation() {
570
// Simulate long-running operation
571
try {
572
Thread.sleep(2000);
573
} catch (InterruptedException e) {
574
Thread.currentThread().interrupt();
575
}
576
return "Long operation completed";
577
}
578
579
private String performDelayedOperation() {
580
return "Delayed operation completed at " + System.currentTimeMillis();
581
}
582
}
583
```