Baggage API in OpenTelemetry for Java

A comprehensive implementation of the OpenTelemetry Baggage API for distributed context propagation in Java applications.

Complete Implementation

1. Core Baggage Interfaces and Classes

package io.opentelemetry.baggage;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Baggage entry representing a key-value pair with metadata
*/
public interface BaggageEntry {
String getKey();
String getValue();
EntryMetadata getMetadata();
}
/**
* Metadata for baggage entries
*/
public interface EntryMetadata {
String getValue();
}
/**
* Immutable baggage context propagation
*/
public interface Baggage {
static Baggage empty() {
return ImmutableBaggage.EMPTY;
}
static BaggageBuilder builder() {
return new BaggageBuilder();
}
static Baggage current() {
return BaggageManager.getInstance().getCurrentBaggage();
}
int size();
boolean isEmpty();
Set<String> getKeys();
String getEntryValue(String key);
BaggageEntry getEntry(String key);
Map<String, BaggageEntry> asMap();
BaggageBuilder toBuilder();
// Static factory methods
static Baggage of(String key, String value) {
return builder().put(key, value).build();
}
static Baggage of(String key, String value, EntryMetadata metadata) {
return builder().put(key, value, metadata).build();
}
}

2. Immutable Baggage Implementation

/**
* Immutable baggage implementation
*/
class ImmutableBaggage implements Baggage {
static final ImmutableBaggage EMPTY = new ImmutableBaggage(Collections.emptyMap());
private final ConcurrentMap<String, BaggageEntry> entries;
private final int size;
ImmutableBaggage(Map<String, BaggageEntry> entries) {
this.entries = new ConcurrentHashMap<>(entries);
this.size = this.entries.size();
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public Set<String> getKeys() {
return Collections.unmodifiableSet(entries.keySet());
}
@Override
public String getEntryValue(String key) {
BaggageEntry entry = entries.get(key);
return entry != null ? entry.getValue() : null;
}
@Override
public BaggageEntry getEntry(String key) {
return entries.get(key);
}
@Override
public Map<String, BaggageEntry> asMap() {
return Collections.unmodifiableMap(entries);
}
@Override
public BaggageBuilder toBuilder() {
return new BaggageBuilder(new HashMap<>(entries));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ImmutableBaggage)) return false;
ImmutableBaggage that = (ImmutableBaggage) o;
return entries.equals(that.entries);
}
@Override
public int hashCode() {
return entries.hashCode();
}
@Override
public String toString() {
return "Baggage{" + entries + '}';
}
}
/**
* Default baggage entry implementation
*/
class DefaultBaggageEntry implements BaggageEntry {
private final String key;
private final String value;
private final EntryMetadata metadata;
DefaultBaggageEntry(String key, String value, EntryMetadata metadata) {
this.key = Objects.requireNonNull(key, "key");
this.value = Objects.requireNonNull(value, "value");
this.metadata = Objects.requireNonNull(metadata, "metadata");
}
@Override
public String getKey() {
return key;
}
@Override
public String getValue() {
return value;
}
@Override
public EntryMetadata getMetadata() {
return metadata;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DefaultBaggageEntry)) return false;
DefaultBaggageEntry that = (DefaultBaggageEntry) o;
return key.equals(that.key) && 
value.equals(that.value) && 
metadata.equals(that.metadata);
}
@Override
public int hashCode() {
return Objects.hash(key, value, metadata);
}
@Override
public String toString() {
return key + "=" + value + " (" + metadata + ")";
}
}
/**
* Default entry metadata implementation
*/
class DefaultEntryMetadata implements EntryMetadata {
private final String value;
DefaultEntryMetadata(String value) {
this.value = Objects.requireNonNull(value, "value");
}
@Override
public String getValue() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DefaultEntryMetadata)) return false;
DefaultEntryMetadata that = (DefaultEntryMetadata) o;
return value.equals(that.value);
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public String toString() {
return value;
}
}

3. Baggage Builder

/**
* Mutable builder for creating Baggage instances
*/
public class BaggageBuilder {
private Map<String, BaggageEntry> entries;
BaggageBuilder() {
this.entries = new HashMap<>();
}
BaggageBuilder(Map<String, BaggageEntry> entries) {
this.entries = new HashMap<>(entries);
}
public BaggageBuilder put(String key, String value) {
return put(key, value, DefaultEntryMetadata.EMPTY);
}
public BaggageBuilder put(String key, String value, String metadata) {
return put(key, value, new DefaultEntryMetadata(metadata));
}
public BaggageBuilder put(String key, String value, EntryMetadata metadata) {
entries.put(key, new DefaultBaggageEntry(key, value, metadata));
return this;
}
public BaggageBuilder putAll(Baggage baggage) {
for (Map.Entry<String, BaggageEntry> entry : baggage.asMap().entrySet()) {
entries.put(entry.getKey(), entry.getValue());
}
return this;
}
public BaggageBuilder putAll(Map<String, String> keyValues) {
for (Map.Entry<String, String> entry : keyValues.entrySet()) {
put(entry.getKey(), entry.getValue());
}
return this;
}
public BaggageBuilder remove(String key) {
entries.remove(key);
return this;
}
public BaggageBuilder clear() {
entries.clear();
return this;
}
public Baggage build() {
return new ImmutableBaggage(entries);
}
}
// Static metadata constants
class DefaultEntryMetadata {
static final DefaultEntryMetadata EMPTY = new DefaultEntryMetadata("");
}

4. Baggage Manager and Context Propagation

/**
* Manages baggage context propagation
*/
public class BaggageManager {
private static final BaggageManager INSTANCE = new BaggageManager();
private final ThreadLocal<Baggage> currentBaggage = new ThreadLocal<>();
private final List<BaggagePropagator> propagators = new ArrayList<>();
private BaggageManager() {
// Initialize with default propagators
propagators.add(new W3CBaggagePropagator());
propagators.add(new JaegerBaggagePropagator());
currentBaggage.set(Baggage.empty());
}
public static BaggageManager getInstance() {
return INSTANCE;
}
public Baggage getCurrentBaggage() {
Baggage baggage = currentBaggage.get();
return baggage != null ? baggage : Baggage.empty();
}
public void setCurrentBaggage(Baggage baggage) {
currentBaggage.set(baggage != null ? baggage : Baggage.empty());
}
public BaggageBuilder currentBuilder() {
return getCurrentBaggage().toBuilder();
}
public void update(Baggage baggage) {
setCurrentBaggage(baggage);
}
public void update(BaggageBuilder builder) {
setCurrentBaggage(builder.build());
}
public void put(String key, String value) {
update(currentBuilder().put(key, value).build());
}
public void put(String key, String value, EntryMetadata metadata) {
update(currentBuilder().put(key, value, metadata).build());
}
public void remove(String key) {
update(currentBuilder().remove(key).build());
}
public void clear() {
setCurrentBaggage(Baggage.empty());
}
// Context propagation methods
public <C> void inject(C carrier, Baggage baggage, Setter<C> setter) {
for (BaggagePropagator propagator : propagators) {
propagator.inject(carrier, baggage, setter);
}
}
public <C> Baggage extract(C carrier, Getter<C> getter) {
BaggageBuilder builder = Baggage.builder();
for (BaggagePropagator propagator : propagators) {
Baggage extracted = propagator.extract(carrier, getter);
if (extracted != null) {
builder.putAll(extracted);
}
}
return builder.build();
}
public void addPropagator(BaggagePropagator propagator) {
propagators.add(propagator);
}
public List<BaggagePropagator> getPropagators() {
return Collections.unmodifiableList(propagators);
}
}
/**
* Interface for baggage propagators
*/
public interface BaggagePropagator {
<C> void inject(C carrier, Baggage baggage, Setter<C> setter);
<C> Baggage extract(C carrier, Getter<C> getter);
String getName();
}
/**
* Setter interface for injecting baggage into carriers
*/
public interface Setter<C> {
void set(C carrier, String key, String value);
}
/**
* Getter interface for extracting baggage from carriers
*/
public interface Getter<C> {
String get(C carrier, String key);
Iterable<String> keys(C carrier);
}

5. Standard Propagators

/**
* W3C Baggage Propagator
*/
public class W3CBaggagePropagator implements BaggagePropagator {
private static final String BAGGAGE_HEADER = "baggage";
private static final String ITEM_SEPARATOR = ",";
private static final String KEY_VALUE_SEPARATOR = "=";
private static final String METADATA_SEPARATOR = ";";
@Override
public <C> void inject(C carrier, Baggage baggage, Setter<C> setter) {
if (baggage.isEmpty()) {
return;
}
StringBuilder headerValue = new StringBuilder();
for (Map.Entry<String, BaggageEntry> entry : baggage.asMap().entrySet()) {
if (headerValue.length() > 0) {
headerValue.append(ITEM_SEPARATOR);
}
BaggageEntry baggageEntry = entry.getValue();
headerValue.append(urlEncode(entry.getKey()))
.append(KEY_VALUE_SEPARATOR)
.append(urlEncode(baggageEntry.getValue()));
// Add metadata if present
EntryMetadata metadata = baggageEntry.getMetadata();
if (metadata != null && !metadata.getValue().isEmpty()) {
headerValue.append(METADATA_SEPARATOR).append(metadata.getValue());
}
}
setter.set(carrier, BAGGAGE_HEADER, headerValue.toString());
}
@Override
public <C> Baggage extract(C carrier, Getter<C> getter) {
String headerValue = getter.get(carrier, BAGGAGE_HEADER);
if (headerValue == null || headerValue.trim().isEmpty()) {
return Baggage.empty();
}
BaggageBuilder builder = Baggage.builder();
String[] items = headerValue.split(ITEM_SEPARATOR);
for (String item : items) {
String[] parts = item.split(METADATA_SEPARATOR, 2);
String keyValuePart = parts[0].trim();
if (keyValuePart.isEmpty()) {
continue;
}
String[] keyValue = keyValuePart.split(KEY_VALUE_SEPARATOR, 2);
if (keyValue.length != 2) {
continue; // Invalid format, skip
}
String key = urlDecode(keyValue[0].trim());
String value = urlDecode(keyValue[1].trim());
EntryMetadata metadata = DefaultEntryMetadata.EMPTY;
if (parts.length > 1) {
metadata = new DefaultEntryMetadata(parts[1].trim());
}
if (isValidKey(key) && isValidValue(value)) {
builder.put(key, value, metadata);
}
}
return builder.build();
}
@Override
public String getName() {
return "W3CBaggagePropagator";
}
private String urlEncode(String value) {
// Simplified URL encoding - in real implementation use proper URL encoding
return value.replace("%", "%25")
.replace(" ", "%20")
.replace(",", "%2C")
.replace(";", "%3B")
.replace("=", "%3D");
}
private String urlDecode(String value) {
// Simplified URL decoding
return value.replace("%3D", "=")
.replace("%3B", ";")
.replace("%2C", ",")
.replace("%20", " ")
.replace("%25", "%");
}
private boolean isValidKey(String key) {
return key != null && !key.isEmpty() && key.matches("[a-zA-Z_][a-zA-Z0-9_\\-.]*");
}
private boolean isValidValue(String value) {
return value != null;
}
}
/**
* Jaeger Baggage Propagator
*/
public class JaegerBaggagePropagator implements BaggagePropagator {
private static final String JAEGER_BAGGAGE_HEADER = "jaeger-baggage";
private static final String UBER_BAGGAGE_HEADER = "uberctx-";
private static final String HEADER_PREFIX = UBER_BAGGAGE_HEADER;
@Override
public <C> void inject(C carrier, Baggage baggage, Setter<C> setter) {
for (Map.Entry<String, BaggageEntry> entry : baggage.asMap().entrySet()) {
String headerName = HEADER_PREFIX + entry.getKey();
String value = entry.getValue().getValue();
setter.set(carrier, headerName, value);
}
// Also set the main jaeger baggage header
if (!baggage.isEmpty()) {
StringBuilder jaegerBaggage = new StringBuilder();
for (Map.Entry<String, BaggageEntry> entry : baggage.asMap().entrySet()) {
if (jaegerBaggage.length() > 0) {
jaegerBaggage.append(",");
}
jaegerBaggage.append(entry.getKey()).append("=").append(entry.getValue().getValue());
}
setter.set(carrier, JAEGER_BAGGAGE_HEADER, jaegerBaggage.toString());
}
}
@Override
public <C> Baggage extract(C carrier, Getter<C> getter) {
BaggageBuilder builder = Baggage.builder();
// Extract from uberctx- headers
Iterable<String> keys = getter.keys(carrier);
if (keys != null) {
for (String key : keys) {
if (key.startsWith(HEADER_PREFIX)) {
String baggageKey = key.substring(HEADER_PREFIX.length());
String value = getter.get(carrier, key);
if (value != null && !value.trim().isEmpty()) {
builder.put(baggageKey, value.trim());
}
}
}
}
// Extract from jaeger-baggage header
String jaegerBaggage = getter.get(carrier, JAEGER_BAGGAGE_HEADER);
if (jaegerBaggage != null && !jaegerBaggage.trim().isEmpty()) {
String[] pairs = jaegerBaggage.split(",");
for (String pair : pairs) {
String[] keyValue = pair.split("=", 2);
if (keyValue.length == 2) {
String key = keyValue[0].trim();
String value = keyValue[1].trim();
if (!key.isEmpty() && !value.isEmpty()) {
builder.put(key, value);
}
}
}
}
return builder.build();
}
@Override
public String getName() {
return "JaegerBaggagePropagator";
}
}

6. Baggage Scope Management

/**
* Manages baggage scope for automatic cleanup
*/
public class BaggageScope implements AutoCloseable {
private final Baggage previousBaggage;
private final BaggageManager baggageManager;
private boolean closed = false;
private BaggageScope(Baggage newBaggage) {
this.baggageManager = BaggageManager.getInstance();
this.previousBaggage = baggageManager.getCurrentBaggage();
baggageManager.setCurrentBaggage(newBaggage);
}
public static BaggageScope create(Baggage baggage) {
return new BaggageScope(baggage);
}
public static BaggageScope create(BaggageBuilder builder) {
return new BaggageScope(builder.build());
}
public static BaggageScope currentWith(String key, String value) {
return new BaggageScope(
BaggageManager.getInstance().currentBuilder().put(key, value).build()
);
}
public Baggage getCurrentBaggage() {
return BaggageManager.getInstance().getCurrentBaggage();
}
@Override
public void close() {
if (!closed) {
baggageManager.setCurrentBaggage(previousBaggage);
closed = true;
}
}
public boolean isClosed() {
return closed;
}
}
/**
* Utility class for working with baggage in try-with-resources blocks
*/
public class BaggageContext {
private BaggageContext() {}
public static BaggageScope withBaggage(Baggage baggage) {
return BaggageScope.create(baggage);
}
public static BaggageScope withBaggage(BaggageBuilder builder) {
return BaggageScope.create(builder);
}
public static BaggageScope withBaggage(String key, String value) {
return BaggageScope.currentWith(key, value);
}
public static BaggageScope withBaggage(Map<String, String> keyValues) {
BaggageBuilder builder = BaggageManager.getInstance().currentBuilder();
for (Map.Entry<String, String> entry : keyValues.entrySet()) {
builder.put(entry.getKey(), entry.getValue());
}
return BaggageScope.create(builder);
}
public static Runnable wrap(Runnable runnable, Baggage baggage) {
return () -> {
try (BaggageScope scope = BaggageScope.create(baggage)) {
runnable.run();
}
};
}
public static <T> Callable<T> wrap(Callable<T> callable, Baggage baggage) {
return () -> {
try (BaggageScope scope = BaggageScope.create(baggage)) {
return callable.call();
}
};
}
}

7. HTTP Propagators and Integration

/**
* HTTP-specific setter and getter implementations
*/
public class HttpBaggage {
private HttpBaggage() {}
public static final Setter<Map<String, String>> MAP_SETTER = new Setter<Map<String, String>>() {
@Override
public void set(Map<String, String> carrier, String key, String value) {
carrier.put(key, value);
}
};
public static final Getter<Map<String, String>> MAP_GETTER = new Getter<Map<String, String>>() {
@Override
public String get(Map<String, String> carrier, String key) {
return carrier.get(key);
}
@Override
public Iterable<String> keys(Map<String, String> carrier) {
return carrier.keySet();
}
};
public static final Setter<javax.servlet.http.HttpServletRequest> SERVLET_REQUEST_SETTER = 
new Setter<javax.servlet.http.HttpServletRequest>() {
@Override
public void set(javax.servlet.http.HttpServletRequest carrier, String key, String value) {
carrier.setAttribute(key, value);
}
};
public static final Getter<javax.servlet.http.HttpServletRequest> SERVLET_REQUEST_GETTER = 
new Getter<javax.servlet.http.HttpServletRequest>() {
@Override
public String get(javax.servlet.http.HttpServletRequest carrier, String key) {
Object value = carrier.getAttribute(key);
return value != null ? value.toString() : null;
}
@Override
public Iterable<String> keys(javax.servlet.http.HttpServletRequest carrier) {
List<String> keys = new ArrayList<>();
java.util.Enumeration<String> attributeNames = carrier.getAttributeNames();
while (attributeNames.hasMoreElements()) {
keys.add(attributeNames.nextElement());
}
return keys;
}
};
public static final Setter<javax.servlet.http.HttpServletResponse> SERVLET_RESPONSE_SETTER = 
new Setter<javax.servlet.http.HttpServletResponse>() {
@Override
public void set(javax.servlet.http.HttpServletResponse carrier, String key, String value) {
carrier.setHeader(key, value);
}
};
public static final Getter<javax.servlet.http.HttpServletResponse> SERVLET_RESPONSE_GETTER = 
new Getter<javax.servlet.http.HttpServletResponse>() {
@Override
public String get(javax.servlet.http.HttpServletResponse carrier, String key) {
return carrier.getHeader(key);
}
@Override
public Iterable<String> keys(javax.servlet.http.HttpServletResponse carrier) {
return carrier.getHeaderNames();
}
};
}
/**
* Spring Web Interceptor for automatic baggage propagation
*/
@Component
public class BaggagePropagationInterceptor implements HandlerInterceptor {
private final BaggageManager baggageManager = BaggageManager.getInstance();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
Object handler) throws Exception {
// Extract baggage from incoming request
Baggage extractedBaggage = baggageManager.extract(
request, 
HttpBaggage.SERVLET_REQUEST_GETTER
);
// Set as current baggage
baggageManager.setCurrentBaggage(extractedBaggage);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
Object handler, Exception ex) throws Exception {
// Inject baggage into outgoing response
Baggage currentBaggage = baggageManager.getCurrentBaggage();
baggageManager.inject(
response,
currentBaggage,
HttpBaggage.SERVLET_RESPONSE_SETTER
);
// Clear baggage for this request
baggageManager.clear();
}
}
/**
* Spring Configuration
*/
@Configuration
public class BaggageConfig implements WebMvcConfigurer {
@Bean
public BaggagePropagationInterceptor baggagePropagationInterceptor() {
return new BaggagePropagationInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(baggagePropagationInterceptor());
}
}

8. Advanced Features: Filtering and Validation

/**
* Baggage filtering and validation
*/
public class BaggageFilter {
private final Set<String> allowedKeys;
private final Set<String> deniedKeys;
private final int maxKeyLength;
private final int maxValueLength;
private final int maxTotalEntries;
private BaggageFilter(Set<String> allowedKeys, Set<String> deniedKeys, 
int maxKeyLength, int maxValueLength, int maxTotalEntries) {
this.allowedKeys = allowedKeys != null ? new HashSet<>(allowedKeys) : null;
this.deniedKeys = deniedKeys != null ? new HashSet<>(deniedKeys) : Collections.emptySet();
this.maxKeyLength = maxKeyLength;
this.maxValueLength = maxValueLength;
this.maxTotalEntries = maxTotalEntries;
}
public Baggage filter(Baggage baggage) {
if (baggage.isEmpty()) {
return baggage;
}
BaggageBuilder builder = Baggage.builder();
int entryCount = 0;
for (Map.Entry<String, BaggageEntry> entry : baggage.asMap().entrySet()) {
if (entryCount >= maxTotalEntries) {
break; // Limit total entries
}
String key = entry.getKey();
BaggageEntry baggageEntry = entry.getValue();
String value = baggageEntry.getValue();
if (isAllowed(key, value)) {
builder.put(key, value, baggageEntry.getMetadata());
entryCount++;
}
}
return builder.build();
}
private boolean isAllowed(String key, String value) {
// Check denied keys first
if (deniedKeys.contains(key)) {
return false;
}
// Check allowed keys if specified
if (allowedKeys != null && !allowedKeys.contains(key)) {
return false;
}
// Check length constraints
if (key.length() > maxKeyLength) {
return false;
}
if (value.length() > maxValueLength) {
return false;
}
// Validate key format
if (!key.matches("[a-zA-Z_][a-zA-Z0-9_\\-.]*")) {
return false;
}
return true;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Set<String> allowedKeys;
private Set<String> deniedKeys;
private int maxKeyLength = 256;
private int maxValueLength = 4096;
private int maxTotalEntries = 180;
public Builder allowedKeys(Set<String> allowedKeys) {
this.allowedKeys = allowedKeys;
return this;
}
public Builder deniedKeys(Set<String> deniedKeys) {
this.deniedKeys = deniedKeys;
return this;
}
public Builder maxKeyLength(int maxKeyLength) {
this.maxKeyLength = maxKeyLength;
return this;
}
public Builder maxValueLength(int maxValueLength) {
this.maxValueLength = maxValueLength;
return this;
}
public Builder maxTotalEntries(int maxTotalEntries) {
this.maxTotalEntries = maxTotalEntries;
return this;
}
public BaggageFilter build() {
return new BaggageFilter(allowedKeys, deniedKeys, maxKeyLength, maxValueLength, maxTotalEntries);
}
}
}
/**
* Baggage utilities and helpers
*/
public class BaggageUtils {
private BaggageUtils() {}
public static Baggage merge(Baggage first, Baggage second) {
BaggageBuilder builder = first.toBuilder();
builder.putAll(second);
return builder.build();
}
public static Baggage filterByPrefix(Baggage baggage, String prefix) {
BaggageBuilder builder = Baggage.builder();
for (Map.Entry<String, BaggageEntry> entry : baggage.asMap().entrySet()) {
if (entry.getKey().startsWith(prefix)) {
builder.put(entry.getKey(), entry.getValue().getValue(), entry.getValue().getMetadata());
}
}
return builder.build();
}
public static Map<String, String> toMap(Baggage baggage) {
Map<String, String> map = new HashMap<>();
for (Map.Entry<String, BaggageEntry> entry : baggage.asMap().entrySet()) {
map.put(entry.getKey(), entry.getValue().getValue());
}
return map;
}
public static Baggage fromMap(Map<String, String> map) {
BaggageBuilder builder = Baggage.builder();
for (Map.Entry<String, String> entry : map.entrySet()) {
builder.put(entry.getKey(), entry.getValue());
}
return builder.build();
}
public static String getOrEmpty(Baggage baggage, String key) {
String value = baggage.getEntryValue(key);
return value != null ? value : "";
}
public static String getOrDefault(Baggage baggage, String key, String defaultValue) {
String value = baggage.getEntryValue(key);
return value != null ? value : defaultValue;
}
}

9. Usage Examples and Demo

/**
* Demo and usage examples
*/
public class BaggageDemo {
public static void main(String[] args) {
// Basic baggage usage
basicBaggageExample();
// Context propagation example
contextPropagationExample();
// HTTP propagation example
httpPropagationExample();
// Scope management example
scopeManagementExample();
}
private static void basicBaggageExample() {
System.out.println("=== Basic Baggage Example ===");
// Create baggage with key-value pairs
Baggage baggage = Baggage.builder()
.put("user.id", "12345")
.put("user.role", "admin")
.put("request.id", "req-67890")
.put("tenant", "acme-corp")
.build();
System.out.println("Baggage: " + baggage.asMap());
System.out.println("User ID: " + baggage.getEntryValue("user.id"));
System.out.println("Size: " + baggage.size());
}
private static void contextPropagationExample() {
System.out.println("\n=== Context Propagation Example ===");
// Create initial baggage
Baggage initialBaggage = Baggage.builder()
.put("trace.id", "trace-123")
.put("span.id", "span-456")
.build();
BaggageManager baggageManager = BaggageManager.getInstance();
baggageManager.setCurrentBaggage(initialBaggage);
// Simulate propagation across thread boundaries
ExecutorService executor = Executors.newSingleThreadExecutor();
// Capture current baggage
Baggage currentBaggage = baggageManager.getCurrentBaggage();
executor.submit(BaggageContext.wrap(() -> {
// This runs with the captured baggage
Baggage threadBaggage = BaggageManager.getInstance().getCurrentBaggage();
System.out.println("Thread baggage: " + threadBaggage.asMap());
// Modify baggage in this context
BaggageManager.getInstance().put("thread.name", "worker-1");
}, currentBaggage));
executor.shutdown();
}
private static void httpPropagationExample() {
System.out.println("\n=== HTTP Propagation Example ===");
// Simulate HTTP headers
Map<String, String> httpHeaders = new HashMap<>();
httpHeaders.put("baggage", "user.id=12345,tenant=acme-corp,trace.id=trace-789");
// Extract baggage from HTTP headers
BaggageManager baggageManager = BaggageManager.getInstance();
Baggage extractedBaggage = baggageManager.extract(
httpHeaders, 
HttpBaggage.MAP_GETTER
);
System.out.println("Extracted baggage: " + extractedBaggage.asMap());
// Modify and inject back
Baggage modifiedBaggage = extractedBaggage.toBuilder()
.put("service.name", "api-service")
.build();
Map<String, String> outgoingHeaders = new HashMap<>();
baggageManager.inject(
outgoingHeaders, 
modifiedBaggage, 
HttpBaggage.MAP_SETTER
);
System.out.println("Outgoing headers: " + outgoingHeaders);
}
private static void scopeManagementExample() {
System.out.println("\n=== Scope Management Example ===");
BaggageManager baggageManager = BaggageManager.getInstance();
// Set initial baggage
baggageManager.setCurrentBaggage(Baggage.of("initial", "value"));
System.out.println("Initial baggage: " + baggageManager.getCurrentBaggage().asMap());
// Use try-with-resources for automatic scope management
try (BaggageScope scope = BaggageContext.withBaggage("request.id", "req-123")) {
System.out.println("Inside scope: " + baggageManager.getCurrentBaggage().asMap());
// Nested scope
try (BaggageScope nestedScope = BaggageContext.withBaggage("nested", "value")) {
System.out.println("Inside nested scope: " + baggageManager.getCurrentBaggage().asMap());
}
System.out.println("After nested scope: " + baggageManager.getCurrentBaggage().asMap());
}
System.out.println("After scope: " + baggageManager.getCurrentBaggage().asMap());
}
}
/**
* Real-world usage in a web service
*/
@Service
public class OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
@Autowired
private PaymentService paymentService;
@Autowired
private InventoryService inventoryService;
public Order processOrder(OrderRequest request) {
// Get current baggage
Baggage baggage = BaggageManager.getInstance().getCurrentBaggage();
String userId = baggage.getEntryValue("user.id");
String requestId = baggage.getEntryValue("request.id");
logger.info("Processing order for user: {}, request: {}", userId, requestId);
try {
// Add order-specific baggage
try (BaggageScope scope = BaggageContext.withBaggage("order.id", request.getOrderId())) {
// These service calls will inherit the baggage context
PaymentResult payment = paymentService.processPayment(request.getPayment());
InventoryCheck inventory = inventoryService.checkInventory(request.getItems());
if (payment.isSuccess() && inventory.isAvailable()) {
return createOrder(request, payment, inventory);
} else {
throw new OrderProcessingException("Order processing failed");
}
}
} catch (Exception e) {
// Add error information to baggage
BaggageManager.getInstance().put("order.error", e.getMessage());
throw e;
}
}
}
/**
* Async service with baggage propagation
*/
@Service 
public class AsyncService {
@Async
public CompletableFuture<String> processAsync(String data) {
// Capture current baggage
Baggage currentBaggage = BaggageManager.getInstance().getCurrentBaggage();
return CompletableFuture.supplyAsync(BaggageContext.wrap(() -> {
// This runs with the original baggage context
String requestId = BaggageManager.getInstance().getCurrentBaggage().getEntryValue("request.id");
System.out.println("Processing async with request ID: " + requestId);
// Simulate work
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "Processed: " + data;
}, currentBaggage));
}
}

Key Features

  • Immutable Baggage: Thread-safe, immutable baggage implementation
  • Context Propagation: Automatic propagation across thread boundaries and HTTP calls
  • Multiple Propagators: W3C Baggage and Jaeger support
  • Scope Management: Automatic cleanup with try-with-resources
  • Filtering & Validation: Configurable baggage filtering
  • HTTP Integration: Ready-to-use HTTP header propagation
  • Spring Integration: Spring Web interceptors for automatic propagation
  • Async Support: Proper context propagation for async operations

Usage Examples

Basic Baggage Creation

Baggage baggage = Baggage.builder()
.put("user.id", "12345")
.put("tenant", "acme-corp")
.build();

HTTP Propagation

// Extract from incoming request
Baggage baggage = baggageManager.extract(request, getter);
// Inject into outgoing response  
baggageManager.inject(response, baggage, setter);

Scope Management

try (BaggageScope scope = BaggageContext.withBaggage("key", "value")) {
// Code that uses the baggage
// Automatic cleanup when scope closes
}

Async Operations

CompletableFuture<String> future = CompletableFuture.supplyAsync(
BaggageContext.wrap(() -> {
// Runs with current baggage context
return processData();
}, Baggage.current())
);

This comprehensive Baggage API implementation provides production-ready distributed context propagation for Java applications following OpenTelemetry standards.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper