0
# Thread Context Management
1
2
Per-thread context functionality providing both Map (MDC) and Stack (NDC) capabilities for request correlation, user tracking, and contextual logging across application layers.
3
4
## Capabilities
5
6
### ThreadContext Map Operations (MDC)
7
8
Key-value pairs associated with the current thread for request correlation and contextual information.
9
10
```java { .api }
11
/**
12
* Thread-local Map operations for Mapped Diagnostic Context (MDC)
13
*/
14
public final class ThreadContext {
15
// Map operations
16
/** Put a key-value pair in the thread context */
17
public static void put(String key, String value);
18
19
/** Put a key-value pair only if the key doesn't exist */
20
public static void putIfNull(String key, String value);
21
22
/** Put all entries from a map into the thread context */
23
public static void putAll(Map<String, String> contextMap);
24
25
/** Get value by key from thread context */
26
public static String get(String key);
27
28
/** Remove a key from the thread context */
29
public static void remove(String key);
30
31
/** Remove multiple keys from the thread context */
32
public static void removeAll(Iterable<String> keys);
33
34
/** Clear the entire thread context map */
35
public static void clearMap();
36
37
/** Get a mutable copy of the current thread context */
38
public static Map<String, String> getContext();
39
40
/** Get an immutable view of the current thread context */
41
public static Map<String, String> getImmutableContext();
42
43
/** Check if a key exists in the thread context */
44
public static boolean containsKey(String key);
45
46
/** Check if the thread context map is empty */
47
public static boolean isEmpty();
48
49
/** Empty immutable map constant */
50
public static final Map<String, String> EMPTY_MAP;
51
}
52
```
53
54
**Usage Examples:**
55
56
```java
57
private static final Logger logger = LogManager.getLogger();
58
59
public void demonstrateThreadContextMap() {
60
// Basic key-value operations
61
ThreadContext.put("userId", "12345");
62
ThreadContext.put("sessionId", "abc-def-ghi");
63
ThreadContext.put("requestId", UUID.randomUUID().toString());
64
65
logger.info("Processing user request"); // Logs will include context
66
67
// Conditional put
68
ThreadContext.putIfNull("environment", "production");
69
70
// Bulk operations
71
Map<String, String> contextData = new HashMap<>();
72
contextData.put("correlationId", "corr-123");
73
contextData.put("operation", "userLogin");
74
ThreadContext.putAll(contextData);
75
76
// Reading context
77
String userId = ThreadContext.get("userId");
78
logger.debug("Current user ID: {}", userId);
79
80
// Context existence check
81
if (ThreadContext.containsKey("adminMode")) {
82
logger.info("Admin mode is active");
83
}
84
85
// Get full context for processing
86
Map<String, String> fullContext = ThreadContext.getContext();
87
processWithContext(fullContext);
88
89
// Cleanup
90
ThreadContext.remove("operation");
91
ThreadContext.removeAll(Arrays.asList("userId", "sessionId"));
92
ThreadContext.clearMap(); // Clear everything
93
}
94
95
// Web request example
96
@RestController
97
public class UserController {
98
private static final Logger logger = LogManager.getLogger();
99
100
@PostMapping("/users/{userId}/profile")
101
public ResponseEntity<UserProfile> updateProfile(
102
@PathVariable String userId,
103
@RequestBody ProfileRequest request,
104
HttpServletRequest httpRequest) {
105
106
// Set up context for entire request
107
ThreadContext.put("userId", userId);
108
ThreadContext.put("requestId", httpRequest.getHeader("X-Request-ID"));
109
ThreadContext.put("endpoint", "/users/{id}/profile");
110
ThreadContext.put("method", "POST");
111
112
try {
113
logger.info("Starting profile update"); // Includes all context
114
115
UserProfile profile = userService.updateProfile(userId, request);
116
117
logger.info("Profile update completed successfully");
118
return ResponseEntity.ok(profile);
119
120
} catch (Exception e) {
121
logger.error("Profile update failed", e); // Context included in error
122
return ResponseEntity.status(500).build();
123
124
} finally {
125
ThreadContext.clearMap(); // Clean up after request
126
}
127
}
128
}
129
```
130
131
### ThreadContext Stack Operations (NDC)
132
133
Stack-based Nested Diagnostic Context for tracking hierarchical execution flow.
134
135
```java { .api }
136
/**
137
* Thread-local Stack operations for Nested Diagnostic Context (NDC)
138
*/
139
public final class ThreadContext {
140
// Stack operations
141
/** Push a message onto the thread context stack */
142
public static void push(String message);
143
144
/** Push a formatted message onto the stack */
145
public static void push(String message, Object... args);
146
147
/** Pop the top message from the stack and return it */
148
public static String pop();
149
150
/** Peek at the top message without removing it */
151
public static String peek();
152
153
/** Clear the entire stack */
154
public static void clearStack();
155
156
/** Get the current stack depth */
157
public static int getDepth();
158
159
/** Trim the stack to the specified depth */
160
public static void trim(int depth);
161
162
/** Create a copy of the current stack */
163
public static ContextStack cloneStack();
164
165
/** Get an immutable view of the current stack */
166
public static ContextStack getImmutableStack();
167
168
/** Empty immutable stack constant */
169
public static final ThreadContextStack EMPTY_STACK;
170
171
/**
172
* Stack interface extending Collection<String>
173
*/
174
public interface ContextStack extends Collection<String>, Serializable {
175
String pop();
176
String peek();
177
void push(String message);
178
int getDepth();
179
List<String> asList();
180
void trim(int depth);
181
ContextStack copy();
182
}
183
}
184
```
185
186
**Usage Examples:**
187
188
```java
189
private static final Logger logger = LogManager.getLogger();
190
191
public void demonstrateThreadContextStack() {
192
// Push operation context
193
ThreadContext.push("userService");
194
logger.info("Starting user operations"); // Stack context included
195
196
try {
197
ThreadContext.push("validateUser");
198
validateUser("12345");
199
ThreadContext.pop(); // Remove validateUser
200
201
ThreadContext.push("updateProfile");
202
updateUserProfile("12345");
203
ThreadContext.pop(); // Remove updateProfile
204
205
} finally {
206
ThreadContext.pop(); // Remove userService
207
}
208
}
209
210
// Nested operation tracking
211
public class OrderProcessor {
212
private static final Logger logger = LogManager.getLogger();
213
214
public void processOrder(String orderId) {
215
ThreadContext.push("processOrder[" + orderId + "]");
216
logger.info("Starting order processing");
217
218
try {
219
validateOrder(orderId);
220
processPayment(orderId);
221
fulfillOrder(orderId);
222
223
logger.info("Order processing completed");
224
} finally {
225
ThreadContext.pop();
226
}
227
}
228
229
private void validateOrder(String orderId) {
230
ThreadContext.push("validateOrder");
231
logger.debug("Validating order");
232
233
try {
234
// Validation logic
235
checkInventory(orderId);
236
checkCustomer(orderId);
237
} finally {
238
ThreadContext.pop();
239
}
240
}
241
242
private void checkInventory(String orderId) {
243
ThreadContext.push("checkInventory");
244
logger.trace("Checking inventory availability");
245
246
try {
247
// Inventory check logic
248
} finally {
249
ThreadContext.pop();
250
}
251
}
252
}
253
254
// Stack manipulation
255
public void demonstrateStackOperations() {
256
ThreadContext.push("operation1");
257
ThreadContext.push("operation2");
258
ThreadContext.push("operation3");
259
260
logger.info("Current depth: {}", ThreadContext.getDepth()); // 3
261
logger.info("Top operation: {}", ThreadContext.peek()); // operation3
262
263
// Trim to specific depth
264
ThreadContext.trim(1); // Keeps only operation1
265
266
// Clone stack for processing
267
ContextStack stackCopy = ThreadContext.cloneStack();
268
processStackCopy(stackCopy);
269
270
ThreadContext.clearStack();
271
}
272
```
273
274
### CloseableThreadContext
275
276
Auto-closeable ThreadContext for try-with-resources usage ensuring automatic cleanup.
277
278
```java { .api }
279
/**
280
* Auto-closeable ThreadContext for automatic cleanup
281
*/
282
public class CloseableThreadContext {
283
/** Put a key-value pair with automatic cleanup */
284
public static Instance put(String key, String value);
285
286
/** Put multiple key-value pairs with automatic cleanup */
287
public static Instance putAll(Map<String, String> values);
288
289
/** Push a message with automatic cleanup */
290
public static Instance push(String message);
291
292
/** Push multiple messages with automatic cleanup */
293
public static Instance pushAll(List<String> messages);
294
295
/**
296
* Auto-closeable instance for cleanup
297
*/
298
public static class Instance implements AutoCloseable {
299
/** Restore previous thread context state */
300
@Override
301
public void close();
302
}
303
}
304
```
305
306
**Usage Examples:**
307
308
```java
309
private static final Logger logger = LogManager.getLogger();
310
311
public void demonstrateCloseableThreadContext() {
312
// Automatic cleanup with try-with-resources
313
try (CloseableThreadContext.Instance ctc =
314
CloseableThreadContext.put("operation", "userUpdate")) {
315
316
logger.info("Starting user update"); // Includes operation context
317
updateUser();
318
logger.info("User update completed");
319
320
} // Context automatically restored here
321
322
// Multiple context values
323
try (CloseableThreadContext.Instance ctc =
324
CloseableThreadContext.putAll(Map.of(
325
"userId", "12345",
326
"sessionId", "abc-def",
327
"operation", "profileUpdate"))) {
328
329
processProfileUpdate();
330
331
} // All context automatically cleaned up
332
333
// Stack operations with cleanup
334
try (CloseableThreadContext.Instance ctc =
335
CloseableThreadContext.push("batchProcessing")) {
336
337
processBatch();
338
339
} // Stack automatically popped
340
}
341
342
// Web filter example
343
@Component
344
public class RequestContextFilter implements Filter {
345
346
@Override
347
public void doFilter(ServletRequest request, ServletResponse response,
348
FilterChain chain) throws IOException, ServletException {
349
350
HttpServletRequest httpRequest = (HttpServletRequest) request;
351
352
// Set up context for entire request with automatic cleanup
353
try (CloseableThreadContext.Instance ctc =
354
CloseableThreadContext.putAll(Map.of(
355
"requestId", httpRequest.getHeader("X-Request-ID"),
356
"userAgent", httpRequest.getHeader("User-Agent"),
357
"remoteAddr", httpRequest.getRemoteAddr(),
358
"method", httpRequest.getMethod(),
359
"uri", httpRequest.getRequestURI()))) {
360
361
chain.doFilter(request, response);
362
363
} // Context automatically cleaned up after request
364
}
365
}
366
367
// Nested closeable contexts
368
public void nestedContextExample() {
369
try (CloseableThreadContext.Instance outerCtc =
370
CloseableThreadContext.put("service", "userService")) {
371
372
logger.info("Service level logging");
373
374
try (CloseableThreadContext.Instance innerCtc =
375
CloseableThreadContext.put("operation", "createUser")) {
376
377
logger.info("Operation level logging"); // Both contexts present
378
createUser();
379
380
} // operation context cleaned up
381
382
logger.info("Back to service level"); // Only service context present
383
384
} // service context cleaned up
385
}
386
```
387
388
### Context Inheritance and Threading
389
390
Understanding how ThreadContext behaves across thread boundaries and async operations.
391
392
```java { .api }
393
// ThreadContext behavior in different threading scenarios
394
public class ThreadContextBehavior {
395
private static final Logger logger = LogManager.getLogger();
396
397
public void demonstrateThreadBehavior() {
398
// Set context in main thread
399
ThreadContext.put("mainThread", "value1");
400
logger.info("Main thread logging"); // Includes context
401
402
// Context is NOT inherited by new threads
403
new Thread(() -> {
404
logger.info("New thread logging"); // NO context from main thread
405
406
// Each thread has its own context
407
ThreadContext.put("workerThread", "value2");
408
logger.info("Worker thread with context");
409
}).start();
410
411
// Main thread still has its context
412
logger.info("Back in main thread"); // Still includes mainThread context
413
}
414
415
// Manual context transfer for async operations
416
public CompletableFuture<String> asyncWithContext() {
417
// Capture context in current thread
418
Map<String, String> contextMap = ThreadContext.getContext();
419
ContextStack contextStack = ThreadContext.cloneStack();
420
421
return CompletableFuture.supplyAsync(() -> {
422
// Restore context in async thread
423
try (CloseableThreadContext.Instance ctc =
424
CloseableThreadContext.putAll(contextMap)) {
425
426
// Restore stack manually if needed
427
for (String stackItem : contextStack.asList()) {
428
ThreadContext.push(stackItem);
429
}
430
431
try {
432
logger.info("Async operation with context");
433
return performAsyncOperation();
434
} finally {
435
ThreadContext.clearStack();
436
}
437
}
438
});
439
}
440
}
441
```
442
443
**Usage Examples:**
444
445
```java
446
// Executor service with context propagation
447
public class ContextAwareExecutor {
448
private final ExecutorService executor = Executors.newFixedThreadPool(10);
449
private static final Logger logger = LogManager.getLogger();
450
451
public <T> CompletableFuture<T> executeWithContext(Supplier<T> task) {
452
// Capture current thread context
453
Map<String, String> contextMap = ThreadContext.getContext();
454
ContextStack contextStack = ThreadContext.cloneStack();
455
456
return CompletableFuture.supplyAsync(() -> {
457
try (CloseableThreadContext.Instance ctc =
458
CloseableThreadContext.putAll(contextMap)) {
459
460
// Restore stack
461
contextStack.asList().forEach(ThreadContext::push);
462
463
try {
464
return task.get();
465
} finally {
466
ThreadContext.clearStack();
467
}
468
}
469
}, executor);
470
}
471
}
472
473
// Spring async method with context
474
@Service
475
public class AsyncService {
476
private static final Logger logger = LogManager.getLogger();
477
478
@Async
479
public CompletableFuture<String> processAsync(String data) {
480
// Context must be manually propagated in @Async methods
481
logger.info("Async processing started"); // May not have context
482
483
try {
484
String result = performProcessing(data);
485
logger.info("Async processing completed");
486
return CompletableFuture.completedFuture(result);
487
} catch (Exception e) {
488
logger.error("Async processing failed", e);
489
throw e;
490
}
491
}
492
}
493
```