OpenTelemetry Context propagation mechanism for carrying scoped values across API boundaries and between threads in Java applications
—
Implicit context values allow objects to store themselves in context without exposing explicit context keys. This pattern is useful for objects that need to be context-aware but want to hide their internal storage mechanism from users.
Objects implementing this interface can be stored in context and automatically manage their own context keys.
interface ImplicitContextKeyed {
Scope makeCurrent();
Context storeInContext(Context context);
}Adds this value to the current context and makes the new context current.
Scope makeCurrent();Returns: A Scope that must be closed to restore the previous context
This method is equivalent to Context.current().with(this).makeCurrent().
Usage Example:
// Assuming Span implements ImplicitContextKeyed
Span span = tracer.spanBuilder("operation").startSpan();
try (Scope scope = span.makeCurrent()) {
// Span is now available in current context
performOperation();
} // Previous context restoredReturns a new context with this value stored in it.
Context storeInContext(Context context);Parameters:
context - The context to store this value inReturns: A new Context containing this value
It's generally recommended to use Context.with(ImplicitContextKeyed) instead of calling this method directly.
Usage Example:
Span span = tracer.spanBuilder("operation").startSpan();
// These are equivalent:
Context contextWithSpan1 = Context.current().with(span);
Context contextWithSpan2 = span.storeInContext(Context.current());public class TraceSpan implements ImplicitContextKeyed {
private static final ContextKey<TraceSpan> SPAN_KEY = ContextKey.named("span");
private final String spanId;
private final String operationName;
public TraceSpan(String spanId, String operationName) {
this.spanId = spanId;
this.operationName = operationName;
}
@Override
public Scope makeCurrent() {
return Context.current().with(this).makeCurrent();
}
@Override
public Context storeInContext(Context context) {
return context.with(SPAN_KEY, this);
}
// Static method to get current span
public static TraceSpan current() {
return Context.current().get(SPAN_KEY);
}
// Span-specific methods
public void setTag(String key, String value) {
// Implementation
}
public void end() {
// Implementation
}
}
// Usage
TraceSpan span = new TraceSpan("span-123", "database-query");
try (Scope scope = span.makeCurrent()) {
span.setTag("query", "SELECT * FROM users");
performDatabaseQuery();
span.end();
}public class RequestContext implements ImplicitContextKeyed {
private static final ContextKey<RequestContext> REQUEST_KEY = ContextKey.named("request");
private final String requestId;
private final String userId;
private final Map<String, String> attributes;
public RequestContext(String requestId, String userId) {
this.requestId = requestId;
this.userId = userId;
this.attributes = new ConcurrentHashMap<>();
}
@Override
public Scope makeCurrent() {
return Context.current().with(this).makeCurrent();
}
@Override
public Context storeInContext(Context context) {
return context.with(REQUEST_KEY, this);
}
public static RequestContext current() {
return Context.current().get(REQUEST_KEY);
}
// Request-specific methods
public String getRequestId() { return requestId; }
public String getUserId() { return userId; }
public void setAttribute(String key, String value) {
attributes.put(key, value);
}
public String getAttribute(String key) {
return attributes.get(key);
}
}
// Usage
RequestContext request = new RequestContext("req-456", "user-789");
try (Scope scope = request.makeCurrent()) {
request.setAttribute("action", "login");
processRequest();
}public class Baggage implements ImplicitContextKeyed {
private static final ContextKey<Baggage> BAGGAGE_KEY = ContextKey.named("baggage");
private final Map<String, String> values;
private Baggage(Map<String, String> values) {
this.values = Collections.unmodifiableMap(new HashMap<>(values));
}
public static Baggage empty() {
return new Baggage(Collections.emptyMap());
}
public static Baggage current() {
Baggage baggage = Context.current().get(BAGGAGE_KEY);
return baggage != null ? baggage : empty();
}
@Override
public Scope makeCurrent() {
return Context.current().with(this).makeCurrent();
}
@Override
public Context storeInContext(Context context) {
return context.with(BAGGAGE_KEY, this);
}
public Baggage put(String key, String value) {
Map<String, String> newValues = new HashMap<>(values);
newValues.put(key, value);
return new Baggage(newValues);
}
public String get(String key) {
return values.get(key);
}
public Set<String> keys() {
return values.keySet();
}
}
// Usage
Baggage baggage = Baggage.current()
.put("user-type", "premium")
.put("region", "us-west");
try (Scope scope = baggage.makeCurrent()) {
// Baggage values available throughout call chain
processWithBaggage();
}public class OperationBuilder {
private String operationName;
private Map<String, String> tags = new HashMap<>();
public OperationBuilder setName(String name) {
this.operationName = name;
return this;
}
public OperationBuilder addTag(String key, String value) {
tags.put(key, value);
return this;
}
public Operation build() {
return new Operation(operationName, tags);
}
public Scope buildAndMakeCurrent() {
return build().makeCurrent();
}
}
// Usage
try (Scope scope = new OperationBuilder()
.setName("user-registration")
.addTag("method", "email")
.buildAndMakeCurrent()) {
registerUser();
}public void processUserRequest(String userId, String requestId) {
// Chain multiple implicit context values
RequestContext request = new RequestContext(requestId, userId);
TraceSpan span = new TraceSpan("span-123", "process-request");
Context enrichedContext = Context.current()
.with(request) // Uses ImplicitContextKeyed.storeInContext()
.with(span); // Uses ImplicitContextKeyed.storeInContext()
try (Scope scope = enrichedContext.makeCurrent()) {
// Both request and span are available
RequestContext currentRequest = RequestContext.current();
TraceSpan currentSpan = TraceSpan.current();
performOperation();
}
}public class ConditionalContextManager {
public static Scope applyUserContext(String userId) {
if (userId != null && !userId.isEmpty()) {
UserContext userContext = new UserContext(userId);
return userContext.makeCurrent();
} else {
return Scope.noop();
}
}
}
// Usage
try (Scope scope = ConditionalContextManager.applyUserContext(getUserId())) {
// User context applied only if user ID is present
performOperation();
}public class HierarchicalContext implements ImplicitContextKeyed {
private static final ContextKey<HierarchicalContext> KEY = ContextKey.named("hierarchical");
private final String level;
private final HierarchicalContext parent;
private HierarchicalContext(String level, HierarchicalContext parent) {
this.level = level;
this.parent = parent;
}
public static HierarchicalContext root(String level) {
return new HierarchicalContext(level, null);
}
public HierarchicalContext child(String childLevel) {
return new HierarchicalContext(childLevel, this);
}
public static HierarchicalContext current() {
return Context.current().get(KEY);
}
@Override
public Scope makeCurrent() {
return Context.current().with(this).makeCurrent();
}
@Override
public Context storeInContext(Context context) {
return context.with(KEY, this);
}
public List<String> getHierarchy() {
List<String> hierarchy = new ArrayList<>();
HierarchicalContext current = this;
while (current != null) {
hierarchy.add(0, current.level); // Add to front
current = current.parent;
}
return hierarchy;
}
}
// Usage
HierarchicalContext root = HierarchicalContext.root("application");
try (Scope rootScope = root.makeCurrent()) {
HierarchicalContext service = HierarchicalContext.current().child("user-service");
try (Scope serviceScope = service.makeCurrent()) {
HierarchicalContext operation = HierarchicalContext.current().child("get-user");
try (Scope operationScope = operation.makeCurrent()) {
List<String> hierarchy = HierarchicalContext.current().getHierarchy();
// hierarchy = ["application", "user-service", "get-user"]
}
}
}public class CompositeContext implements ImplicitContextKeyed {
private static final ContextKey<CompositeContext> KEY = ContextKey.named("composite");
private final Map<String, Object> values;
private CompositeContext(Map<String, Object> values) {
this.values = Collections.unmodifiableMap(new HashMap<>(values));
}
public static CompositeContext empty() {
return new CompositeContext(Collections.emptyMap());
}
public static CompositeContext current() {
CompositeContext context = Context.current().get(KEY);
return context != null ? context : empty();
}
@Override
public Scope makeCurrent() {
return Context.current().with(this).makeCurrent();
}
@Override
public Context storeInContext(Context context) {
return context.with(KEY, this);
}
public <T> CompositeContext with(String key, T value) {
Map<String, Object> newValues = new HashMap<>(values);
newValues.put(key, value);
return new CompositeContext(newValues);
}
@SuppressWarnings("unchecked")
public <T> T get(String key, Class<T> type) {
Object value = values.get(key);
return type.isInstance(value) ? (T) value : null;
}
}
// Usage
CompositeContext composite = CompositeContext.current()
.with("requestId", "req-123")
.with("timestamp", System.currentTimeMillis())
.with("feature-flags", Set.of("feature-a", "feature-b"));
try (Scope scope = composite.makeCurrent()) {
String requestId = CompositeContext.current().get("requestId", String.class);
Long timestamp = CompositeContext.current().get("timestamp", Long.class);
Set<String> flags = CompositeContext.current().get("feature-flags", Set.class);
}// Good implementation example
public class WellDesignedContext implements ImplicitContextKeyed {
private static final ContextKey<WellDesignedContext> KEY =
ContextKey.named("well-designed-context");
// Immutable fields
private final String id;
private final long timestamp;
public WellDesignedContext(String id) {
this.id = Objects.requireNonNull(id, "id must not be null");
this.timestamp = System.currentTimeMillis();
}
// Thread-safe static access
public static WellDesignedContext current() {
WellDesignedContext context = Context.current().get(KEY);
if (context == null) {
throw new IllegalStateException("No WellDesignedContext in current context");
}
return context;
}
// Safe static access with default
public static WellDesignedContext currentOrDefault() {
WellDesignedContext context = Context.current().get(KEY);
return context != null ? context : new WellDesignedContext("default");
}
@Override
public Scope makeCurrent() {
return Context.current().with(this).makeCurrent();
}
@Override
public Context storeInContext(Context context) {
return context.with(KEY, this);
}
// Immutable accessors
public String getId() { return id; }
public long getTimestamp() { return timestamp; }
}Install with Tessl CLI
npx tessl i tessl/maven-io-opentelemetry--opentelemetry-context