SigNoz Cloud - This page is relevant for SigNoz Cloud editions.
Self-Host - This page is relevant for self-hosted SigNoz editions.

How to add manual instrumentation in Java

Manual instrumentation lets you capture business operations that automatic instrumentation misses. Use it to track order processing, payment workflows, or any domain-specific logic that matters for debugging and monitoring.

Prerequisites

<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-api</artifactId>
  <version>1.57.0</version>
</dependency>
<dependency>
  <groupId>io.opentelemetry.instrumentation</groupId>
  <artifactId>opentelemetry-instrumentation-annotations</artifactId>
  <version>2.23.0</version>
</dependency>
Info

The versions above are current as of this writing. Check the OpenTelemetry Java releases for the latest versions.

Step 1. Create manual spans

Using annotations (simplest approach)

Annotate methods with @WithSpan to automatically create spans:

OrderService.java
import io.opentelemetry.instrumentation.annotations.WithSpan;
import io.opentelemetry.instrumentation.annotations.SpanAttribute;

public class OrderService {

    @WithSpan("process-order")
    public void processOrder(@SpanAttribute("order.id") String orderId) {
        // A span wraps this entire method
        validateOrder(orderId);
        chargePayment(orderId);
        shipOrder(orderId);
    }

    @WithSpan
    public void validateOrder(@SpanAttribute("order.id") String orderId) {
        // Child span created automatically
    }
}
  • @WithSpan creates a span for the method's lifetime
  • @SpanAttribute captures method parameters as span attributes
  • Nested @WithSpan methods become child spans automatically

Using the SDK (more control)

For fine-grained control, use the tracer API directly:

PaymentService.java
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;

public class PaymentService {
    private static final Tracer tracer = GlobalOpenTelemetry.getTracer("payment-service");

    public void processPayment(String paymentId, double amount) {
        Span span = tracer.spanBuilder("process-payment").startSpan();
        try (Scope scope = span.makeCurrent()) {
            span.setAttribute("payment.id", paymentId);
            span.setAttribute("payment.amount", amount);

            // Your payment logic here
            chargeCard(paymentId, amount);

        } finally {
            span.end();
        }
    }
}

Key points:

  • GlobalOpenTelemetry.getTracer() gets a tracer from the agent's configured provider
  • span.makeCurrent() sets this span as the parent for any child spans
  • Always call span.end() in a finally block

Creating nested spans

Child spans link to their parent automatically when you use makeCurrent():

OrderProcessor.java
public void processOrder(String orderId) {
    Span parentSpan = tracer.spanBuilder("process-order").startSpan();
    try (Scope parentScope = parentSpan.makeCurrent()) {
        parentSpan.setAttribute("order.id", orderId);

        // This span becomes a child of process-order
        Span validateSpan = tracer.spanBuilder("validate-inventory").startSpan();
        try (Scope validateScope = validateSpan.makeCurrent()) {
            validateSpan.setAttribute("warehouse", "WH-001");
            checkInventory(orderId);
        } finally {
            validateSpan.end();
        }

        // Another child span
        Span chargeSpan = tracer.spanBuilder("charge-payment").startSpan();
        try (Scope chargeScope = chargeSpan.makeCurrent()) {
            processPayment(orderId);
        } finally {
            chargeSpan.end();
        }

    } finally {
        parentSpan.end();
    }
}

Step 2. Add attributes and events

Attributes are key-value pairs that show up in SigNoz so you can filter and search traces. Events mark notable moments within a span.

Adding attributes

import io.opentelemetry.api.common.AttributeKey;

Span span = Span.current();

// String attributes
span.setAttribute("user.id", userId);
span.setAttribute("order.status", "processing");

// Numeric attributes
span.setAttribute(AttributeKey.longKey("item.count"), 5L);
span.setAttribute(AttributeKey.doubleKey("total.amount"), 149.99);

// Boolean attributes
span.setAttribute(AttributeKey.booleanKey("premium.customer"), true);

Adding events

Events capture specific moments during span execution:

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributeKey;

Span span = Span.current();

// Simple event
span.addEvent("order.validated");

// Event with attributes
span.addEvent("payment.processed", Attributes.of(
    AttributeKey.stringKey("payment.method"), "credit_card",
    AttributeKey.stringKey("transaction.id"), "txn_abc123",
    AttributeKey.doubleKey("amount"), 99.99
));

// Event with timestamp
span.addEvent("inventory.reserved", Attributes.empty(), Instant.now());

Use events for:

  • Cache hits/misses
  • Retry attempts
  • State transitions
  • External API calls

Step 3. Record errors

Flag failures so they surface in SigNoz error views and alerts:

import io.opentelemetry.api.trace.StatusCode;

public void riskyOperation() {
    Span span = Span.current();

    try {
        doSomethingRisky();
        span.setStatus(StatusCode.OK);

    } catch (Exception e) {
        // Record the exception with full stack trace
        span.recordException(e);

        // Mark the span as errored
        span.setStatus(StatusCode.ERROR, e.getMessage());

        // Re-throw so calling code can handle it
        throw e;
    }
}
  • recordException() captures the stack trace and exception details
  • setStatus(StatusCode.ERROR) marks the span red in SigNoz
  • Always re-throw exceptions unless you're intentionally swallowing them

Getting the current span

From anywhere in your code, grab the active span to add context:

import io.opentelemetry.api.trace.Span;

public void someUtilityMethod(String customerId) {
    Span currentSpan = Span.current();

    if (currentSpan.isRecording()) {
        currentSpan.setAttribute("customer.id", customerId);
        currentSpan.addEvent("utility.invoked");
    }
}

Links connect spans that are causally related but not in a parent-child relationship. Common use cases:

  • Batch processing where one trigger creates multiple independent operations
  • Fan-out/fan-in patterns
  • Async message processing
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.context.Context;

// Save the context from the triggering operation
SpanContext triggerContext = Span.current().getSpanContext();

// Later, create a linked span
Span batchItemSpan = tracer.spanBuilder("process-batch-item")
    .addLink(triggerContext)
    .startSpan();

try (Scope scope = batchItemSpan.makeCurrent()) {
    // Process the batch item
} finally {
    batchItemSpan.end();
}

Validate

With your application running, verify traces are being sent to SigNoz:

  1. Trigger the code paths with your manual instrumentation.
  2. In SigNoz, open the Services tab and click Refresh. Your application should appear.
  3. Go to the Traces tab to see your application's traces.

Troubleshooting

Custom spans not appearing?

Check the tracer is initialized:

Tracer tracer = GlobalOpenTelemetry.getTracer("my-service");
// If this returns a no-op tracer, the agent isn't running

Make sure you're running with the Java agent attached.

Verify spans are ended:

// Wrong - span never ends
Span span = tracer.spanBuilder("my-span").startSpan();
doWork();

// Correct - span ends in finally
Span span = tracer.spanBuilder("my-span").startSpan();
try {
    doWork();
} finally {
    span.end();
}

Child spans not linking to parent?

Use makeCurrent() to set the parent context:

// Wrong - no parent link
Span child = tracer.spanBuilder("child").startSpan();

// Correct - parent span is current
Span parent = tracer.spanBuilder("parent").startSpan();
try (Scope scope = parent.makeCurrent()) {
    Span child = tracer.spanBuilder("child").startSpan();
    // child is now linked to parent
}

Attributes not showing up?

  • Set attributes before calling span.end()
  • Use the correct attribute types (strings, longs, doubles, booleans)
  • Check attribute names don't have typos

@WithSpan not creating spans?

Make sure:

  1. The Java agent is attached (annotations only work with the agent)
  2. The annotated method is called from outside the class (self-invocation skips the proxy)
  3. You have opentelemetry-instrumentation-annotations in your dependencies

Next steps

Last updated: January 3, 2026

Edit on GitHub

Was this page helpful?