Jooby

OpenTelemetry

The module provides the foundational engine for distributed tracing, metrics, and log correlation in your Jooby application. Its goal is to give you deep, vendor-neutral observability into your system. By integrating the OpenTelemetry SDK, it automatically captures and exports telemetry data from HTTP requests, database connection pools, background jobs, and application logs.

Because OpenTelemetry is an open standard, you are not locked into a specific vendor. You can seamlessly route your telemetry data to any compatible APM, backend, or collector (such as SigNoz, DataDog, Jaeger, or Grafana) simply by changing your configuration properties.

Usage

1) Add the dependency:

Maven
Gradle
<!-- OpenTelemetry Module-->
<dependency>
  <groupId>io.jooby</groupId>
  <artifactId>jooby-opentelemetry</artifactId>
  <version>4.5.1</version>
</dependency>

2) Install and use OpenTelemetry:

Java
Kotlin
import io.jooby.opentelemetry.OtelModule;
import io.jooby.opentelemetry.OtelHttpTracing;

{
  install(new OtelModule());                               (1)

  use(new OtelHttpTracing());                              (2)

  get("/", ctx -> {
    return "Hello OTel";
  });
}
  1. Installs the core OpenTelemetry SDK engine. It must be installed at the very beginning of your application setup.

  2. Adds the OtelHttpTracing filter to automatically intercept, create, and propagate spans for incoming HTTP requests.

Note

JVM Metrics: Basic JVM operational metrics (such as memory usage, garbage collection times, and active thread counts) are automatically bound and exported by default the moment OtelModule is installed.

Exporters Configuration

The OpenTelemetry SDK is completely driven by your application’s configuration properties. Any property defined inside the otel block in your application.conf is automatically picked up by the SDK’s auto-configuration engine.

Here is how you can configure the exporters to send your data to various popular backends:

SigNoz (or generic OTLP)

SigNoz natively accepts the standard OTLP (OpenTelemetry Protocol) format over gRPC.

application.conf
otel {
  service.name = "jooby-api"
  traces.exporter = otlp
  metrics.exporter = otlp
  logs.exporter = otlp
  exporter.otlp.protocol = grpc
  exporter.otlp.endpoint = "http://localhost:4317"
}

DataDog

To send data to DataDog, you typically use the OTLP HTTP protocol pointing to the DataDog Agent running on your infrastructure, or directly to their intake API.

application.conf
otel {
  service.name = "jooby-api"
  traces.exporter = otlp
  metrics.exporter = otlp
  logs.exporter = otlp
  exporter.otlp.protocol = http/protobuf
  exporter.otlp.endpoint = "http://localhost:4318" # Assuming local DataDog Agent
  # If sending directly to DataDog, you would include the API key in headers:
  # exporter.otlp.headers = "DD-API-KEY=your_api_key_here"
}

Jaeger

Jaeger also natively supports accepting OTLP data.

application.conf
otel {
  service.name = "jooby-api"
  traces.exporter = otlp
  metrics.exporter = none     # Jaeger is for traces only
  logs.exporter = none        # Jaeger is for traces only
  exporter.otlp.protocol = grpc
  exporter.otlp.endpoint = "http://localhost:4317"
}

Manual Tracing

For tracing specific business logic, database queries, or external API calls deep within your service layer, this module provides an injectable Trace utility.

You can retrieve it from the route context or inject it directly via DI to safely create and execute custom spans:

Manual Tracing
Java
Kotlin
import io.jooby.opentelemetry.Trace;

{
  get("/books/{isbn}", ctx -> {
    Trace trace = require(Trace.class);
    String isbn = ctx.path("isbn").value();

    return trace.span("fetch_book")
      .attribute("isbn", isbn)
      .execute(span -> {
        span.addEvent("Executing database query");
        return repository.findByIsbn(isbn);
      });
  });
}

The execute and run blocks automatically handle the span context lifecycle, error recording, and finalization, ensuring no spans are leaked even if exceptions are thrown.

Extensions

Additional integrations are provided via OtelExtension implementations. Many of these rely on official OpenTelemetry instrumentation libraries, which you must add to your project’s classpath.

Note

Lifecycle & Lazy Initialization: Although OtelModule must be installed at the very beginning of your application, its extensions are lazily initialized. They defer their execution to the application’s onStarting lifecycle hook. This ensures that all target components provided by other modules (like database connection pools or background schedulers) are fully configured and available in the service registry before the OpenTelemetry extensions attempt to instrument them.

Apache Camel

Seamlessly integrates OpenTelemetry with Apache Camel. By combining Camel’s native OpenTelemetry component with Jooby’s OtelModule, it automatically instruments your Camel routes. It ensures distributed trace contexts remain unbroken whether you are triggering routes synchronously from Jooby HTTP endpoints or consuming messages asynchronously from background brokers (like Kafka, ActiveMQ, or RabbitMQ).

Required dependency:

Maven
Gradle
<dependency>
  <groupId>org.apache.camel</groupId>
  <artifactId>camel-opentelemetry2</artifactId>
  <version>${camel.version}</version>
</dependency>

To activate the instrumentation, you must enable it in your application configuration:

application.conf
camel.opentelemetry2.enabled = true
Camel Integration
Java
Kotlin
import io.jooby.camel.CamelModule;
import io.jooby.opentelemetry.OtelModule;

{
  install(new OtelModule());                               (1)

  install(new CamelModule(new MyCamelRoutes()));           (2)
}
  1. Initializes the global OpenTelemetry SDK. It must be installed at the very beginning of your application setup.

  2. Registers the Camel module. Because camel.opentelemetry2.enabled is set to true, Camel will automatically detect the active tracer provided by OtelModule and weave it into your route lifecycle.

Note

Installation order is critical. OtelModule must be installed before CamelModule so that the global OpenTelemetry SDK is fully initialized before Camel attempts to attach its route interceptors.

db-scheduler

Automatically instruments the db-scheduler library. It tracks background task executions, measuring execution durations and recording successes and failures.

db-scheduler Integration
Java
Kotlin
import io.jooby.opentelemetry.instrumentation.OtelDbScheduler;

{
  install(new DbSchedulerModule()
    .withExecutionInterceptor(new OtelDbScheduler(require(OpenTelemetry.class)))
  );
}

gRPC

Provides automatic tracing, metrics, and context propagation for gRPC services. It instruments both the embedded grpc-java server and loopback channels to ensure seamless distributed traces across your application.

Required dependency:

Maven
Gradle
<dependency>
  <groupId>io.opentelemetry.instrumentation</groupId>
  <artifactId>opentelemetry-grpc-1.6</artifactId>
  <version>${otel-instrumentation.version}</version>
</dependency>
gRPC Integration
Java
Kotlin
import io.jooby.grpc.GrpcModule;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.grpc.v1_6.GrpcTelemetry;

{
  install(new OtelModule());

  var grpcTelemetry = GrpcTelemetry.create(require(OpenTelemetry.class));

  install(new GrpcModule(new GreeterService()
    .withServer(server -> server.intercept(grpcTelemetry.newServerInterceptor()))        // (1)
    .withChannel(channel -> channel.intercept(grpcTelemetry.newClientInterceptor()))     // (2)
  ));
}
  1. newServerInterceptor(): Extracts the distributed trace context from incoming gRPC metadata. It creates a dedicated child span for the specific gRPC method execution, automatically recording its duration and status code when the call completes.

  2. newClientInterceptor(): Grabs the active trace context (typically started by Jooby’s underlying HTTP router) and injects it into the outgoing metadata on the internal loopback channel. This bridges the gap between the HTTP pipeline and the gRPC engine, ensuring a single, unbroken distributed trace.

HikariCP

Instruments all registered HikariDataSource instances to export critical pool metrics (active/idle connections, timeouts).

Required dependency:

Maven
Gradle
<dependency>
  <groupId>io.opentelemetry.instrumentation</groupId>
  <artifactId>opentelemetry-hikaricp-3.0</artifactId>
  <version>${otel-instrumentation.version}</version>
</dependency>
Note

Installation order is critical. OtelModule must be installed before HikariModule.

HikariCP Metrics
Java
Kotlin
import io.jooby.hikari.HikariModule;
import io.jooby.opentelemetry.instrumentation.OtelHikari;

{
  install(new OtelModule(new OtelHikari()));

  install(new HikariModule());
}

JSON-RPC

Provides automatic tracing for your JSON-RPC 2.0 endpoints. By adding the OtelJsonRcpTracing middleware to your JSON-RPC pipeline, it generates a dedicated OpenTelemetry span for every RPC invocation.

It automatically records standard semantic attributes (such as rpc.system, rpc.method, and rpc.jsonrpc.request_id). Furthermore, because it hooks directly into the JsonRpcChain, it accurately records protocol errors and application failures by inspecting the JsonRpcResponse envelope, without relying on thrown exceptions.

JSON-RPC Integration
Java
Kotlin
import io.jooby.jsonrpc.JsonRpcModule;
import io.jooby.jsonrpc.instrumentation.OtelJsonRcpTracing;
import io.opentelemetry.api.OpenTelemetry;

{
  install(new OtelModule());

  // Register the JSON-RPC module and attach the tracing middleware
  install(new JsonRpcModule(new MovieServiceRpc_())
    .invoker(new OtelJsonRcpTracing(require(OpenTelemetry.class)))
  );
}

Model Context Protocol (MCP)

Provides automatic tracing for your MCP (Model Context Protocol) servers. By adding the OtelMcpTracing invoker to your MCP module pipeline, it generates a dedicated OpenTelemetry span for every MCP operation (tools, prompts, resources, and completions).

It strictly follows the official OpenTelemetry GenAI and RPC Semantic Conventions, ensuring seamless integration with modern APM and specialized AI observability dashboards. It prevents metric cardinality explosion by intelligently handling span names, and accurately records both protocol failures and MCP tool errors (which return isError = true rather than throwing exceptions).

MCP Integration
Java
Kotlin
import io.jooby.mcp.McpModule;
import io.jooby.mcp.instrumentation.OtelMcpTracing;
import io.opentelemetry.api.OpenTelemetry;

{
  install(new OtelModule());

  // Register the MCP module and attach the tracing invoker
  install(new McpModule(new CalculatorServiceMcp_())
    .invoker(new OtelMcpTracing(require(OpenTelemetry.class)))
  );
}

Log4j2

Seamlessly exports all application logs to your OpenTelemetry backend, automatically correlated with active trace and span IDs using a dynamic appender.

Required dependency:

Maven
Gradle
<dependency>
  <groupId>io.opentelemetry.instrumentation</groupId>
  <artifactId>opentelemetry-log4j-appender-2.17</artifactId>
  <version>${otel-instrumentation.version}</version>
</dependency>
Log4j2 Integration
Java
Kotlin
import io.jooby.opentelemetry.instrumentation.OtelLog4j2;

{
  install(new OtelModule(
    new OtelLog4j2()
  ));
}

Logback

Seamlessly exports all application logs to your OpenTelemetry backend, automatically correlated with active trace and span IDs using a dynamic appender.

Required dependency:

Maven
Gradle
<dependency>
  <groupId>io.opentelemetry.instrumentation</groupId>
  <artifactId>opentelemetry-logback-appender-1.0</artifactId>
  <version>${otel-instrumentation.version}</version>
</dependency>
Logback Integration
Java
Kotlin
import io.jooby.opentelemetry.instrumentation.OtelLogback;

{
  install(new OtelModule(
    new OtelLogback()
  ));
}

Quartz

Tracks background task executions handled by the Quartz scheduler, creating individual spans for each execution to monitor scheduling delays and execution durations.

Required dependency:

Maven
Gradle
<dependency>
  <groupId>io.opentelemetry.instrumentation</groupId>
  <artifactId>opentelemetry-quartz-2.0</artifactId>
  <version>${otel-instrumentation.version}</version>
</dependency>
Quartz Integration
Java
Kotlin
import io.jooby.quartz.QuartzModule;
import io.jooby.opentelemetry.instrumentation.OtelQuartz;

{
  install(new OtelModule(new OtelQuartz()));

  install(new QuartzModule(MyJobs.class));
}

Server Metrics

Exports native, server-specific operational metrics. It automatically detects your underlying HTTP server (Jetty, Netty, or Undertow) and exports deep metrics like event loop pending tasks, thread pool sizes, and memory usage.

Server Metrics
Java
Kotlin
import io.jooby.opentelemetry.instrumentation.OtelServerMetrics;

{
  install(new OtelModule(
    new OtelServerMetrics()
  ));
}