Micrometer Tracing Integration
The fun-tracing module instruments Try and Result executions with distributed tracing
spans automatically, opening a named child span for each call and recording outcome signals
without requiring manual Span.start() / Span.end() management at call sites. It is an
optional dependency — the core fun library has no runtime dependency on Micrometer Tracing.
fun-tracing declares micrometer-tracing as compileOnly. You bring your own version
and bridge (OpenTelemetry or Brave), following the same pattern as fun-micrometer and
fun-resilience4j. Any version from 1.2.x through 1.6.x is supported
(see version compatibility below).
Adding the dependency
// Gradleimplementation("codes.domix:fun-tracing:LATEST_VERSION")// Micrometer Tracing — bring your own version (1.2.x – 1.6.x)implementation("io.micrometer:micrometer-tracing:1.6.5")// Add a bridge — OTel (recommended) or Brave:implementation("io.micrometer:micrometer-tracing-bridge-otel:1.6.5")// implementation("io.micrometer:micrometer-tracing-bridge-brave:1.6.5")<!-- Maven --><dependency> <groupId>codes.domix</groupId> <artifactId>fun-tracing</artifactId> <version>LATEST_VERSION</version></dependency><!-- Micrometer Tracing — bring your own version (1.2.x – 1.6.x) --><dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing</artifactId> <version>1.6.5</version></dependency><!-- Add a bridge — OTel (recommended) or Brave --><dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bridge-otel</artifactId> <version>1.6.5</version></dependency>
fun-tracingdeclaresmicrometer-tracingascompileOnly. You bring your own version and bridge — Spring Boot auto-configures aTracerbean when either bridge is on the classpath.
Signals recorded
For every instrumented call, the following signals are written to the active span:
| Signal | When |
|---|---|
Span named {name} | Always — the span name is the name argument |
outcome=success tag | Supplier returned normally |
outcome=failure tag | Supplier threw any exception |
exception=<label> tag | Supplier threw — classifier label of the cause |
| Span marked as error | Supplier threw — recorded via span.error(cause) |
The span is always ended before the method returns, even when the supplier throws.
Span naming convention
Use stable, low-cardinality operation names. A practical convention is:
<bounded-context>.<operation>
Examples:
payment.chargeinventory.reserveorder.placehttp.client.get
Avoid embedding IDs or user input in span names — those belong in log fields or baggage, not in span names, as high-cardinality names generate excessive trace index entries in Zipkin, Jaeger, and similar backends.
exception tag cardinality
Warning: The default
exceptiontag usesgetClass().getSimpleName(). When arbitrary third-party exceptions can appear at runtime, this produces an unbounded number of distinct tag values. High-cardinality span tags generate excessive index entries in Zipkin, Jaeger, and similar backends — and bloat sampling metadata in OpenTelemetry exporters.
In production, always supply an explicit classifier that maps every reachable exception type to one of a small, fixed set of labels:
DmxTracing dmx = DmxTracing.of(tracer, cause -> switch (cause) { case IOException __ -> "io"; case TimeoutException __ -> "timeout"; case IllegalStateException __-> "state"; default -> "other"; });The same option is available on the DmxTraced builder:
DmxTraced.of("payment.charge") .tracer(tracer) .exceptionClassifier(cause -> cause instanceof IOException ? "io" : "other") .traceTry(() -> stripe.charge(amount));DmxTracing
The primary entry point. Create one instance per Tracer (typically application-scoped)
and call traceTry or traceResult at each instrumentation point.
import dmx.fun.Result;import dmx.fun.Try;import dmx.fun.tracing.DmxTracing;import io.micrometer.tracing.Tracer;
// One-time setup — typically injected from the application contextDmxTracing dmx = DmxTracing.of(tracer); // io.micrometer.tracing.Tracer
// Instrument a Try operation — name becomes the span nameTry<Response> result = dmx.traceTry("http.client.get", () -> httpClient.get(url));
result .onSuccess(r -> render(r)) .onFailure(ex -> renderError(ex));
// Instrument a Result operationResult<User, Throwable> user = dmx.traceResult("db.user.find", () -> userRepo.findById(id));Signals recorded for each call:
| Signal | When |
|---|---|
Span named {name} | Always |
outcome=success tag | Supplier returned normally |
outcome=failure tag | Supplier threw |
exception=<SimpleClassName> tag | Supplier threw |
| Span marked as error | Supplier threw |
DmxTraced — fluent builder
When you prefer to configure name and tracer in a single chain — for example inside a
factory method or when wiring the span name and tracer at different call sites — use the
DmxTraced fluent builder:
import dmx.fun.Try;import dmx.fun.Result;import dmx.fun.tracing.DmxTraced;
// Chain span name → tracer → executeTry<Receipt> result = DmxTraced.of("payment.charge") .tracer(tracer) .traceTry(() -> stripe.charge(amount));
// Same with ResultResult<User, Throwable> user = DmxTraced.of("db.user.find") .tracer(tracer) .traceResult(() -> userRepo.findById(id));
DmxTracedis a mutable builder — calltraceTryortraceResultonce at the end. For repeated instrumentation of the same operation, preferDmxTracing.of(tracer)and calltraceTrydirectly.
Choosing DmxTracing vs DmxTraced
| Scenario | Recommendation |
|---|---|
Spring Boot with fun-spring-boot | Inject DmxTracing directly — auto-configured, no setup needed |
| Multiple operations in the same class (plain Spring) | DmxTracing.of(tracer) — inject Tracer, wrap once in the constructor |
| Single ad-hoc operation or factory method | DmxTraced.of(name).tracer(tracer).traceTry(...) |
Real-world example
An order service where every step — inventory reservation, payment charge, and persistence — is instrumented as an independent child span. No manual span bookkeeping in the business logic; every step appears in the trace timeline automatically.
import dmx.fun.Result;import dmx.fun.Try;import dmx.fun.tracing.DmxTracing;import io.micrometer.tracing.Tracer;
/** * An order service where each step — inventory reservation, payment charge, * and persistence — is wrapped in its own named span. * * After a request completes, the trace backend (Zipkin, Jaeger, etc.) shows * a timeline of child spans, each tagged with outcome=success|failure. * Failed steps also carry exception=<ClassName> and are marked as errors, * making the root cause visible at a glance without digging into logs. */public class OrderService {
private final DmxTracing dmx; private final PaymentGateway paymentGateway; private final InventoryClient inventoryClient; private final OrderRepository orderRepository;
public OrderService(Tracer tracer, PaymentGateway paymentGateway, InventoryClient inventoryClient, OrderRepository orderRepository) { this.dmx = DmxTracing.of(tracer); this.paymentGateway = paymentGateway; this.inventoryClient = inventoryClient; this.orderRepository = orderRepository; }
/** * Places an order: reserves inventory, charges payment, and persists. * Each step is a child span of the active trace context — no manual * Span.start() / Span.end() calls needed. */ public Result<Order, String> placeOrder(PlaceOrderCommand cmd) {
// 1. Reserve inventory — span: inventory.reserve Try<Reservation> reservation = dmx.traceTry("inventory.reserve", () -> inventoryClient.reserve(cmd.items()) ); if (reservation.isFailure()) { return Result.err("Inventory unavailable: " + reservation.getCause().getMessage()); }
// 2. Charge payment — span: payment.charge Try<Receipt> receipt = dmx.traceTry("payment.charge", () -> paymentGateway.charge(cmd.amount(), cmd.paymentToken()) ); if (receipt.isFailure()) { inventoryClient.release(reservation.get()); return Result.err("Payment failed: " + receipt.getCause().getMessage()); }
// 3. Persist order — span: order.place return dmx.traceResult("order.place", () -> orderRepository.save(Order.from(cmd, reservation.get(), receipt.get())) ) .mapError(ex -> "Failed to persist order: " + ex.getMessage()); }}Spring Boot integration
When fun-spring-boot, fun-tracing, and a Micrometer Tracing bridge are all on the
classpath, Spring Boot registers a DmxTracing bean automatically — no @Bean declaration
or manual wiring required. Inject it directly into your services:
import dmx.fun.Try;import dmx.fun.tracing.DmxTracing;import org.springframework.stereotype.Service;
@Servicepublic class PaymentService {
private final DmxTracing dmx; // injected — auto-configured by fun-spring-boot
public PaymentService(DmxTracing dmx) { this.dmx = dmx; }
public Try<Receipt> charge(Amount amount, PaymentToken token) { return dmx.traceTry("payment.charge", () -> gateway.charge(amount, token) ); }}How it works
DmxFunTracingAutoConfiguration activates when:
micrometer-tracingandfun-tracingare on the classpath (@ConditionalOnClass).- A
Tracerbean is present in the context (@ConditionalOnBean) — Spring Boot registers one automatically when a bridge (micrometer-tracing-bridge-otelormicrometer-tracing-bridge-brave) is on the classpath.
The DmxTracing bean is backed by that Tracer, so all spans it opens inherit the active
trace context (W3C TraceContext, B3, etc.) without any extra wiring.
Opting out
dmx.fun.tracing.enabled=false # disable DmxTracing auto-configurationReplacing the bean
The bean is guarded by @ConditionalOnMissingBean. Declare your own @Bean of type
DmxTracing and the auto-configuration backs off:
@BeanDmxTracing dmxTracing(Tracer tracer) { return DmxTracing.of(tracer); // or any custom Tracer}Without fun-spring-boot
If you use plain Spring (no fun-spring-boot), construct DmxTracing manually in your
@Bean or constructor:
@Servicepublic class PaymentService {
private final DmxTracing dmx;
public PaymentService(Tracer tracer) { this.dmx = DmxTracing.of(tracer); }}Version compatibility
The module is tested in CI against the following Micrometer Tracing versions on every
pull request that touches the tracing/ module:
| Micrometer Tracing | Spring Boot | Status |
|---|---|---|
| 1.6.x | 3.5.x | tested |
| 1.5.x | 3.4.x | tested |
| 1.4.x | 3.3.x | tested |
| 1.3.x | 3.2.x | tested |
| 1.2.x | 3.1.x | tested |
To test locally against a specific version:
./gradlew :tracing:test -PmicrometerTracingVersion=1.5.11