OpenTelemetry Context propagation mechanism for carrying scoped values across API boundaries and between threads in Java applications
—
Context keys provide type-safe indexing for values stored in contexts, while scopes manage the lifecycle of context attachments with automatic cleanup.
Creates a new context key with an optional debug name.
static <T> ContextKey<T> named(String name);Parameters:
name - Debug name for the key (does not affect behavior)Returns: A new ContextKey instance
Usage Example:
// Create typed context keys
private static final ContextKey<String> USER_ID_KEY = ContextKey.named("userId");
private static final ContextKey<Integer> REQUEST_COUNT_KEY = ContextKey.named("requestCount");
private static final ContextKey<List<String>> TAGS_KEY = ContextKey.named("tags");
// Use keys for type-safe storage and retrieval
Context context = Context.current()
.with(USER_ID_KEY, "user123")
.with(REQUEST_COUNT_KEY, 42)
.with(TAGS_KEY, Arrays.asList("tag1", "tag2"));
// Type-safe retrieval
String userId = context.get(USER_ID_KEY); // String
Integer count = context.get(REQUEST_COUNT_KEY); // Integer
List<String> tags = context.get(TAGS_KEY); // List<String>Context keys are compared by reference equality, not by name. Each call to named() creates a distinct key.
// These are different keys, even with same name
ContextKey<String> key1 = ContextKey.named("user");
ContextKey<String> key2 = ContextKey.named("user");
Context ctx = Context.current().with(key1, "alice");
String value1 = ctx.get(key1); // Returns "alice"
String value2 = ctx.get(key2); // Returns null - different key!Best Practices:
static final fieldspublic class UserContext {
// Good: single key instance shared across usage
private static final ContextKey<String> USER_ID_KEY = ContextKey.named("userId");
public static Context withUserId(String userId) {
return Context.current().with(USER_ID_KEY, userId);
}
public static String getUserId() {
return Context.current().get(USER_ID_KEY);
}
}Represents a mounted context that must be closed to restore the previous context.
interface Scope extends AutoCloseable {
static Scope noop();
void close();
}Returns a scope that does nothing when closed.
static Scope noop();Used internally when attaching a context that's already current.
Usage Example:
// Conditional scope creation
public Scope attachIfNeeded(Context context) {
if (Context.current() != context) {
return context.makeCurrent();
} else {
return Scope.noop(); // No change needed
}
}Closes the scope and restores the previous context.
void close();Usage with try-with-resources:
Context newContext = Context.current().with(USER_KEY, user);
try (Scope scope = newContext.makeCurrent()) {
// Context is active here
performOperation();
} // Scope automatically closed, previous context restoredManual closing (not recommended):
Context newContext = Context.current().with(USER_KEY, user);
Scope scope = newContext.makeCurrent();
try {
performOperation();
} finally {
scope.close(); // Must close manually
}private static final ContextKey<String> OPERATION_KEY = ContextKey.named("operation");
public void performUserOperation(String userId, String operation) {
Context operationContext = Context.current()
.with(USER_ID_KEY, userId)
.with(OPERATION_KEY, operation);
try (Scope scope = operationContext.makeCurrent()) {
// Context is available to all called methods
logOperationStart();
executeOperation();
logOperationEnd();
} // Previous context automatically restored
}
private void logOperationStart() {
String userId = Context.current().get(USER_ID_KEY);
String operation = Context.current().get(OPERATION_KEY);
logger.info("Starting {} for user {}", operation, userId);
}public void processRequest(String requestId) {
Context requestContext = Context.current().with(REQUEST_ID_KEY, requestId);
try (Scope requestScope = requestContext.makeCurrent()) {
logger.info("Processing request: {}", requestId);
for (String userId : getUsers()) {
Context userContext = Context.current().with(USER_ID_KEY, userId);
try (Scope userScope = userContext.makeCurrent()) {
// Both request ID and user ID available
processUser();
} // User context ends, request context continues
}
} // Request context ends, original context restored
}Scopes are automatically closed even when exceptions occur:
public void riskyOperation() {
Context context = Context.current().with(OPERATION_KEY, "risky");
try (Scope scope = context.makeCurrent()) {
// Context is available
performRiskyOperation();
if (someCondition) {
throw new RuntimeException("Operation failed");
}
} // Scope closed even if exception thrown
catch (RuntimeException e) {
// Previous context is already restored
logger.error("Operation failed", e);
throw e;
}
}public void conditionalContext(String userId) {
boolean needsUserContext = userId != null && !userId.isEmpty();
Scope scope = needsUserContext
? Context.current().with(USER_ID_KEY, userId).makeCurrent()
: Scope.noop();
try (scope) {
performOperation();
}
}public class ContextualService {
private final Service delegate;
public void serviceMethod(Context context) {
try (Scope scope = context.makeCurrent()) {
// Service method runs with provided context
delegate.performAction();
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-opentelemetry--opentelemetry-context