Skip to content
Switch branches/tags

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

Build Status Released Version

Span manager for Java

Warning: This library is still a work in progress!
Please watch pull-request #115 in opentracing-java (or clean copy #127 with less comments) for progress on inclusion in the standard Java OpenTracing libraries.

This library provides a way to manage spans and propagate them to other threads.


Defines current span management.

A SpanManager separates the creation of a Span from its use later on. This relieves application developers from passing the current span around through their code. Only tracing-related code will need access to a SpanManager reference, provided as an ordinary dependency.

SpanManager provides the following methods:

  1. activate(span) makes the given span the current managed span.
    Returns a ManagedSpan containing a deactivate() method to later 'unmanage' the span with.
  2. current() returns the current ManagedSpan, or an empty managed span object without a Span if no span is activated.
  3. clear() provides unconditional cleanup of all managed spans for the current process.


A default SpanManager maintaining a Stack-like ThreadLocal storage of linked managed spans.

Deactivating a linked managed span uses the following algorithm:

  1. If the deactivated span is not the current span, the current span is left alone.
  2. Otherwise, the first parent that is not yet deactivated is set as the new current span.
  3. If no current parents remain, the current span is cleared.
  4. Consecutive deactivate() calls for already-deactivated spans will be ignored.



This ExecutorService propagates the current span from the caller into each call that is executed.
The current span of the caller is obtained from the configured SpanManager.

Please note: The current span is merely propagated into the background thread (as-is).
It is explicitly not finished when the calls end, nor will new spans be automatically related to the propagated span.


This convenience Tracer automates managing the current span:

  1. It wraps another Tracer.
  2. Spans created with this tracer are:
    • automatically activated when started, and
    • automatically deactivated when finished.


Manually propagating any Span into a background thread

To propagate a Span into a new Thread, the current Span from the caller must be remembered by the Runnable:

    class ExampleRunnable implements Runnable {
        private final SpanManager spanManager;
        private final Span currentSpanFromCaller;
        ExampleRunnable(SpanManager spanManager) {
            this(spanManager, NoopSpan.INSTANCE);
        private ExampleRunnable(SpanManager spanManager, Span currentSpanFromCaller) {
            this.spanManager = spanManager;
            this.currentSpanFromCaller = currentSpanFromCaller;
        ExampleRunnable withCurrentSpan() {
            return new ExampleRunnable(spanManager, spanManager.currentSpan());
        public void run() {
            try (ManagedSpan parent = spanManager.activate(currentSpanFromCaller)) {

                // Any background code that requires tracing
                // and may use spanManager.current().getSpan()
            } // parent.deactivate() restores spanManager.current()

Then the application can propagate this current Span into background threads:

    class App {
        public static void main(String... args) throws InterruptedException {
            Config config = ...;
            Tracer tracer = config.getTracer();
            SpanManager spanManager = config.getSpanManager();
            ExampleRunnable runnable = new ExampleRunnable(spanManager);

            try (Span appSpan = tracer.buildSpan("main").start();           // start appSpan
                    ManagedSpan managed = spanManager.activate(appSpan)) {  // update current Span
                Thread example = new Thread(runnable.withCurrentSpan());
            } // managed.deactivate() + appSpan.finish()

Threadpool that propagates SpanManager.current().getSpan() into threads

    class TracedCall implements Callable<String> {
        SpanManager spanManager = ... // inject or DefaultSpanManager.getInstance();
        public String call() {
            Span currentSpan = spanManager.current().getSpan(); // Propagated span from caller
            // ...

    class Caller {
        SpanManager spanManager = ... // inject or DefaultSpanManager.getInstance(); 
        ExecutorService threadpool = new SpanPropagatingExecutorService(anyThreadpool(), spanManager);

        void run() {
            // ...code that sets the current Span somewhere:
            try (ManagedSpan current = spanManager.activate(someSpan)) {
                // scheduling the traced call:
                Future<String> result = threadpool.submit(new TracedCall());

Propagating threadpool with 'ManagedSpan' Tracer

When starting a new span and making it the current Span, the manual example above used:

    try (Span span = tracer.buildSpan("main").start();           // start span
            ManagedSpan managed = spanManager.activate(span)) {  // update current Span
        // ...traced block of code...

The ManagedSpanTracer automatically makes every started span the current span. It also deactivates it again when the span is finished:

    class Caller {
        SpanManager spanManager = ... // inject or DefaultSpanManager.getInstance();
        Tracer tracer = new ManagedSpanTracer(anyTracer(), spanManager);
        ExecutorService threadpool = new SpanPropagatingExecutorService(anyThreadpool(), spanManager);

        void run() {
            try (Span parent = tracer.buildSpan("parentOperation").start()) { // parent == current Span
                // Scheduling the traced call:
                Future<String> result = threadpool.submit(new TracedCall());
            } // parent.finish() + ((ManagedSpan) parent).deactivate()

Example with asynchronous request / response filters

When asynchronous processing is handled by separate request/response filters, a try-with-resources code block is insufficient.

Existing filters that start / finish new spans asynchronously can simply be supplied with the ManagedSpanTracer around the existing tracer. This sets the current Span from the request filter and calls deactivate() automatically from the response filter when the existing filter finishes the span.
An example would be using the opentracing jaxrs filters in combination with the ManagedSpanTracer:

    // Add example when opentracing jaxrs library stabilizes

Alternatively, the following hypothetic filter pair could be used on an asynchronous server:
Handling the request:

    final SpanManager spanManager = ... // inject or DefaultSpanManager.getInstance();
    void onRequest(RequestContext reqCtx) {
        Span span = ... // either obtain Span from previous filter or start from the request
        ManagedSpan managedSpan = spanManager.activate(span); // span is now current Span.
        reqCtx.put(SOMEKEY, managedSpan);

For the response:

    final SpanManager spanManager = ...
    void onResponse(RequestContext reqCtx, ResponseContext resCtx) {
        spanManager.clear(); // Clear stack containing the current Span if this is a boundary-filter
        // or: 
        // ManagedSpan managedSpan = reqCtx.get(SOMEKEY);
        // managedSpan.deactivate();
        // If the corresponding request filter starts a span, don't forget to call span.finish() here!