Logs Metrics & Traces

Jaeger and OpenTelemetry for distributed tracing: how they work together

Chris Churilo
August 27, 2026
 |  
7
min read
August 27, 2026
7
min read
Logs Metrics & Traces

Comparing Jaeger and OpenTelemetry head-to-head is the wrong framing because they operate at different layers of the tracing stack. OpenTelemetry is a vendor-neutral framework for instrumenting applications and collecting telemetry. It collects traces and metrics. It also collects logs. Jaeger is a backend that stores traces and makes them searchable in a UI. They occupy different layers of the same pipeline, and since Jaeger v2 they share code: Jaeger builds its binary on the OpenTelemetry Collector framework itself.

That division of labor is not a recent accident. Jaeger’s own maintainers described it as the plan back in 2019: the project was never meant to solve the code instrumentation problem, and by shipping OpenTracing-compatible tracers it could lean on an existing instrumentation ecosystem and spend its effort on the backend. The client libraries were the one place the two projects overlapped, and Jaeger retired them in 2022 and now tells users to instrument with OTel. OTel, for its part, removed its Jaeger-format exporter because Jaeger accepts OTLP natively.

The real decisions you face are architectural: which collector and storage backend to run. You must also decide where to sample and how to deploy all of it on Kubernetes.

Jaeger vs OpenTelemetry at a glance

The comparison below maps each project to its layer in the tracing stack:

Dimension OpenTelemetry Jaeger
Scope Instrumentation and collection of telemetry; export to configured backends Trace storage, query, and visualization
Signals Traces and metrics. Logs are also supported (Profiles in alpha) Traces only
Role in pipeline Generates telemetry and routes it to backends Receives traces and stores them for display
Instrumentation SDKs across the major production languages, plus auto-instrumentation None (Jaeger maintainers retired native clients in 2022)
Collection OTel Collector (agent, sidecar, or gateway) Jaeger v2 binary, which uses the OTel Collector framework as its foundation
Storage None (exports to backends) Cassandra, Elasticsearch, OpenSearch, ClickHouse (experimental), Badger, memory
UI / visualization None Query UI and service map. The Monitor tab provides SPM
Protocol OTLP over gRPC and HTTP Jaeger has accepted OTLP natively since v1.35; it enabled OTLP by default in v1.46
Governance CNCF, graduated May 11, 2026 CNCF, graduated October 31, 2019

OpenTelemetry instruments; Jaeger stores and visualizes traces

OTel handles instrumentation and collection; Jaeger handles storage and visualization. For a self-hosted open-source tracing stack, you run both. OTel SDKs generate spans inside your services, and an OTel Collector receives and processes them. Jaeger stores the spans and renders the trace views your on-call engineer opens at 2 AM. Jaeger’s own docs draw the line the same way: OpenTelemetry supplies the APIs and SDKs that get telemetry out of the process, and Jaeger is primarily the backend that receives, processes, aggregates and visualizes it.

It is worth being precise about what has actually changed since 2019, because the split was never really a fork in the road for users. Jaeger’s tracers implemented OpenTracing, so choosing Jaeger clients was choosing OpenTracing rather than choosing against OTel’s predecessors. What Jaeger’s creator flagged at the time was narrower: the client libraries occupied the same problem space as the merged OpenTelemetry project, maintaining them across many languages was expensive, and the sensible endgame was to freeze them and port Jaeger-specific features like adaptive sampling upstream. That is roughly what happened. The Jaeger maintainers have archived Jaeger’s clients, the OTel Collector dropped its Jaeger-format exporter, and Jaeger v2 is built on the OTel Collector framework with storage adapters and a query UI attached.

What is OpenTelemetry?

OpenTelemetry is the CNCF’s vendor-neutral standard for generating and collecting telemetry. It also exports telemetry. CNCF announced its graduation on May 21, 2026, following a third-party security audit and formal governance review; the CNCF project page records May 11, 2026 as the date the project moved to Graduated maturity. Traces and metrics are stable at the specification level. The logs bridge API is also stable. A Profiles signal entered public alpha in 2026.

For this tracing pipeline, the project gives you two main components. The SDKs live inside your application: they create spans and propagate context across service boundaries. They export telemetry over OTLP, OTel’s wire protocol.

The OTel Collector runs as separate infrastructure. It receives telemetry and applies processing such as batching and sampling. The Collector then exports to one or more backends.

Because the Collector decouples instrumentation from destination, you can reroute traces from Jaeger to another backend, or to several at once, by editing a config file rather than touching application code.

OTel formed in May 2019 from the merger of OpenTracing and OpenCensus, and CNCF framed it at the time as both projects’ successor. CNCF archived OpenTracing in January 2022, and OpenCensus maintainers archived all repositories except opencensus-python in July 2023. If you still run either, OTel provides compatibility shims, but the standard has one name now.

What is Jaeger?

Jaeger is a distributed tracing platform, and in practice that means a tracing backend. It receives spans and writes them to a storage backend. Its query service and UI let you explore traces, including a service map. It graduated from the CNCF in October 2019.

The Jaeger team published the current stable release, v2.20.0, on July 20, 2026. Jaeger v1 reached end of life on December 31, 2025.

Jaeger’s scope is deliberately narrow. The v2.20 API docs state it plainly: Jaeger accepts only tracing data because it does not store other telemetry types.

There is no metrics ingestion, no log storage, no alerting, and no general-purpose dashboards. The project’s own architecture docs point you to the OpenTelemetry Collector when you need to handle metrics and logs alongside traces.

The one metrics-adjacent feature is Service Performance Monitoring (SPM), which surfaces Request and Error metrics in the UI’s Monitor tab. It also surfaces Duration metrics at P95, P75 and P50, completing the RED model. Jaeger derives those metrics from span data in one of two ways: the OTel SpanMetrics Connector pre-computes them into a PromQL-compatible backend, or Jaeger Query computes them at query time directly against Elasticsearch or OpenSearch. Either way they are a trace-derived view, and the SPM HTTP API is documented as internal. Run a separate metrics pipeline for metrics ingestion and retention. Use that pipeline for alerting too.

How they work together: the OTLP pipeline

The standard integration path runs entirely over OTLP: an OTel SDK in each service exports spans to an OTel Collector, and the Collector’s OTLP exporter forwards them to Jaeger. Jaeger listens on the same default ports as the Collector: 4317 for OTLP over gRPC and 4318 for OTLP over HTTP at the /v1/traces path. Jaeger added the OTLP receiver in v1.35.0 in May 2022 and enabled by default in v1.46.0 in June 2023; on versions in between, set COLLECTOR_OTLP_ENABLED=true.

OTLP is the recommended path. Jaeger still accepts its legacy Protobuf and Thrift formats and Zipkin formats, but the docs mark the Jaeger-native ones deprecated and maintained only for backwards compatibility.

The OTel Collector maintainers removed the Jaeger exporter

If your Collector config still references a jaeger exporter, that component is no longer in the released Collector binaries. The contrib maintainers deprecated the jaegerexporter and jaegerthrifthttpexporter components in v0.72.0 (February 2023). They dropped the components from the Collector distribution manifest in v0.85.0 (September 2023). The v0.86.0 CHANGELOG records the removal. The migration guide explains that Jaeger’s native OTLP support prompted this change.

Migration is a config edit. Before:

exporters:
  jaeger:
    endpoint: https://jaeger.example.com:14250
service:
  pipelines:
    exporters: [jaeger]

After:

exporters:
  otlp/jaeger:
    endpoint: https://jaeger.example.com:4317
service:
  pipelines:
    exporters: [otlp/jaeger]

Jaeger deprecated its native SDKs

Jaeger’s client libraries are gone, and this settles the instrumentation question decisively in OTel’s favor. The official migration page states: “Jaeger clients have been retired in 2022. Please use the OpenTelemetry SDKs.” The wind-down followed a published schedule. The maintainers accepted PRs through the end of 2021. They began a six-month code freeze in January 2022 with security fixes only, then archived the repositories.

All six client repositories now carry a deprecation banner. The Jaeger maintainers archived the Java, Python, Node, and C# clients in 2022; the Go client followed in May 2024.

The host-agent story is separate and often misreported. The standalone jaeger-agent binary from v1 is gone, but v2 did not delete the concept: the v2.20 architecture docs still list agent as one of the roles the Jaeger binary can be configured for, described as a host agent or sidecar that forwards trace data to the collector. The docs simply recommend against it, on the grounds that you will likely want the standard OpenTelemetry Collector in that slot anyway to handle metrics and logs. Treat it as deprecated by recommendation rather than removed.

For teams still running Jaeger clients:

  • New applications: Instrument with OTel APIs and SDKs from the start. Use OTel auto-instrumentation where appropriate.
  • Existing OpenTracing applications: Replace the Jaeger client with the corresponding OTel SDK plus the OpenTracing shim, available in most supported languages, so existing instrumentation calls keep working during the transition.
  • Export path: Point the OTel SDK’s OTLP exporter at your Jaeger backend directly, or at an OTel Collector in front of it. No Jaeger-specific exporter is needed on either side.

OTel Collector vs Jaeger Collector: which should you use?

Jaeger v2 mostly dissolves this question, because its collector is an OTel Collector: the binary is a custom distribution of the OpenTelemetry Collector framework, bundling upstream components (OTLP receiver, batch and attributes processors, tail sampling), contrib components (Kafka exporter and receiver), and Jaeger’s own storage exporter, query extension, adaptive sampling processor and remote sampling extension. You can even build your own distribution with the OpenTelemetry Collector Builder (ocb).

So the honest answer is that you do not need a separate OTel Collector to run Jaeger, and the Jaeger docs say so directly. You add one in front of Jaeger when it earns its place, which it usually does:

  • Flexibility: Its component catalog covers receivers and processors for far more than traces. Exporters route the resulting telemetry. One Collector deployment can handle traces to Jaeger and metrics to Prometheus. It can also send logs to a log backend, with shared processing such as batching and Kubernetes attribute enrichment.
  • Portability: Export targets live in config rather than code, so you can swap or add backends without re-instrumenting. Every major vendor accepts OTLP, so the Collector keeps your exit options open.
  • Continuity: It even preserves Jaeger-specific features. The OTel Collector can serve Jaeger’s remote sampling protocol, either from static config or by proxying requests to the Jaeger backend for adaptive sampling.

The Jaeger docs describe three shapes for those front-line Collectors: application sidecar, host agent or daemon, and remote service cluster. Sidecar and host-agent deployments simplify SDK configuration (both the trace endpoint and the sampling endpoint become localhost) and distribute enrichment cost across application hosts. A remote cluster buys you sharding, which is what tail-based sampling needs. Both add a marshaling hop. The question reduces to how many Collector instances you run in front of Jaeger, which is a topology decision covered below.

Jaeger storage backends

Jaeger does not store traces itself; you provision a backend, and this choice determines your operational burden more than any feature comparison.

Storage is configured through the jaeger_storage extension, which holds a map of named backends. You can define more than one, and Jaeger uses that to support a second, longer-retention tier: the primary store takes every ingested trace under a short TTL, and an archive store keeps individual traces indefinitely when a user saves them by hand from the UI. The archive backend does not have to be the same type as the primary. SPM adds a parallel metric_backends map on top of that. What you cannot do is point one query service at several primary trace stores at once: jaeger_query references exactly one backend name for traces and one for traces_archive.

The production options break down as follows:

  • OpenSearch / Elasticsearch: The Jaeger team recommends OpenSearch over Cassandra for large-scale production. Saving a span is a single write, and the node handles indexing. Search is far more capable, and tools like Kibana can query the store directly. Jaeger supports Elasticsearch 7.x and 8.x, with three index management strategies: time-based indices (the default), manual rollover, and rollover with ILM. ILM is the recommended one because Elasticsearch then handles rollover and retention itself, removing the rollover, lookback and index-cleaner cron jobs.
  • Cassandra: Retrieval by trace ID is more efficient, and native TTL simplifies retention. But writes are amplified, since each span incurs extra writes for service name and operation name. Every tag also adds a write. Jaeger implements search client-side on top of key-value storage, which the Jaeger FAQ calls limited and potentially inconsistent, and the FAQ’s own conclusion is that overall throughput ends up comparable to OpenSearch. Jaeger supports versions 4.x and 5.x.
  • ClickHouse: SigNoz runs ClickHouse as its primary and stable trace store. For Jaeger, v2.20 supports ClickHouse version 26.x but classifies it as officially experimental behind the storage.clickhouse feature gate, warning that the schema is subject to breaking changes in future releases. Keep it out of production Jaeger deployments until the schema stabilizes.
  • Badger: Badger is an embedded key-value store that writes to the local filesystem. It survives restarts and works only in all-in-one configuration or behind the remote storage component. Badger cannot scale horizontally, so the docs limit it to modest data volumes. For a single-node deployment it removes an external dependency, with no cluster to operate.
  • In-memory: The in-memory backend loses data when the process exits. Use it for development and testing.

A gRPC Remote Storage API (v2, stable) exists for custom backends. Jaeger lists community implementations such as a PostgreSQL backend, but these are not maintained by the project, and issue 638 is where additional backends get discussed.

Kafka is not a trace store. It sits between collection and writing as a persistent buffer: collectors export to Kafka, and a separate ingester role reads from Kafka and writes to storage. Multiple ingesters partition the load automatically. Use it when you want to survive storage backpressure rather than drop spans; you still need a queryable trace store behind it.

One constraint worth knowing before you plan around Jaeger’s UI: you cannot point Jaeger Query at trace data some other exporter wrote into Elasticsearch or ClickHouse. Jaeger’s FAQ is explicit that its collector and query service share a Jaeger-specific schema, so a UI-only deployment over a foreign index layout is unsupported.Sampling strategies: head-based vs tail-based

Where you sample determines what you keep and what you pay. The two mechanisms live at different layers of the stack, which is why this is a joint OTel-and-Jaeger design decision rather than a feature of either tool.

The OTel SDK performs head-based sampling at the moment a trace starts. The SDK calls ShouldSample and decides before knowing anything about the trace’s outcome. Built-in samplers include AlwaysOn, AlwaysOff, TraceIdRatioBased, and ParentBased; the OTEL_TRACES_SAMPLER environment variable defaults to parentbased_always_on, so the SDK samples every trace unless you configure another sampler. Head sampling is cheap because the SDK never exports dropped spans, but the OTel sampling docs name the core weakness: it cannot guarantee retaining error traces, because the decision precedes the error.

The OTel Collector performs tail-based sampling after all or most spans of a trace have arrived. The tailsamplingprocessor (beta) holds traces in memory, 50,000 by default, and waits decision_wait (default 30 seconds). It then applies policies: latency, status_code, probabilistic, string_attribute, rate limiting, and composable and/not/composite logic. You can keep every trace that errored or exceeded a latency threshold while sampling the healthy majority. The cost is memory pressure and one hard architectural constraint: every span of a trace must reach the same Collector instance.

Jaeger’s adaptive sampling operates through the remote sampling protocol. Jaeger observes incoming spans and recalculates probabilities for each service and endpoint to hit a configured target_samples_per_second. New endpoints start at initial_sampling_probability. It requires a sampling_store backend (memory, Cassandra, Badger, Elasticsearch, or OpenSearch). Jaeger v2 also supports tail-based sampling directly, via the same upstream tailsamplingprocessor, since it runs on the Collector framework.

The practical pattern combines them: light head sampling in the SDK to cap volume, tail sampling at a gateway to preserve errors and slow traces. Decide early, because tail sampling constrains your Kubernetes topology.

Sampling strategies: head-based vs tail-based

Where you sample determines what you keep and what you pay. The two mechanisms live at different layers of the stack, which is why this is a joint OTel-and-Jaeger design decision rather than a feature of either tool.

The OTel SDK performs head-based sampling at the moment a trace starts. The SDK calls ShouldSample and decides before knowing anything about the trace’s outcome. Built-in samplers include AlwaysOn, AlwaysOff, TraceIdRatioBased, and ParentBased; the OTEL_TRACES_SAMPLER environment variable defaults to parentbased_always_on, so the SDK samples every trace unless you configure another sampler. Head sampling is cheap because the SDK never exports dropped spans, but the OTel sampling docs name the core weakness: it cannot guarantee retaining error traces, because the decision precedes the error.

The OTel Collector performs tail-based sampling after all or most spans of a trace have arrived. The tailsamplingprocessor (beta) holds traces in memory, 50,000 by default, and waits decision_wait (default 30 seconds). It then applies policies: latency, status_code, probabilistic, string_attribute, rate limiting, and composable and/not/composite logic. You can keep every trace that errored or exceeded a latency threshold while sampling the healthy majority. The cost is memory pressure and one hard architectural constraint: every span of a trace must reach the same Collector instance.

Jaeger’s adaptive sampling operates through the remote sampling protocol. Jaeger observes incoming spans and recalculates probabilities for each service and endpoint to hit a configured target_samples_per_second. New endpoints start at initial_sampling_probability. It requires a sampling_store backend (memory, Cassandra, Badger, Elasticsearch, or OpenSearch). Jaeger v2 also supports tail-based sampling directly, via the same upstream tailsamplingprocessor, since it ships that component in its distribution.

The practical pattern combines them: light head sampling in the SDK to cap volume, tail sampling at a gateway to preserve errors and slow traces. Decide early, because tail sampling constrains your Kubernetes topology.

Deploying Jaeger and the OTel Collector on Kubernetes

Both projects ship Helm charts, and a working pipeline is two installs plus an endpoint setting. You need a cluster and Helm. You also need a storage backend decision from the section above.

The Jaeger chart (v4.12.0, repo https://jaegertracing.github.io/helm-charts) deploys Jaeger v2 as an all-in-one Deployment. Configure OTLP receivers under userconfig:

userconfig:
  receivers:
    otlp:
      protocols:
        grpc:
          endpoint: 0.0.0.0:4317
        http:
          endpoint: 0.0.0.0:4318

The service also exposes the Query UI on 16686 and legacy Jaeger ports. It exposes Zipkin and Prometheus ports as well. Set jaeger.ingress.enabled: true with hosts to expose the UI, or use the Gateway API via jaeger.httproute.enabled. Note that Jaeger UI has no concept of accounts or roles, so if the UI needs to be restricted, the project’s own advice is to put a reverse proxy in front of it.

All-in-one is not your only option. Splitting the collector and query roles lets you scale read and write traffic independently and apply different access policies to each; both shapes scale horizontally as long as storage is external.

The opentelemetry-collector chart (v0.165.0, repo https://open-telemetry.github.io/opentelemetry-helm-charts) requires Kubernetes 1.24+ and Helm 4.0+, and it forces two explicit choices: mode (no default) and image.repository. The chart binds default OTLP receivers to the pod IP rather than 0.0.0.0 to reduce DDoS exposure. The mode setting is your topology decision:

  • DaemonSet: One Collector per node, with a clear one-to-one mapping between applications and their local Collector. It is the preferred home for the Kubeletstats and Filelog receivers. The Host Metrics receiver also sees only its own node. Scaling is vertical, so configure smaller batch sizes and shorter timeouts.
  • Sidecar: The OTel Operator injects a Collector container into each workload pod via the annotation sidecar.opentelemetry.io/inject: "true". The scaling docs recommend it when node count is low and pod count is high, since it gives better gRPC load distribution without a dedicated L7 balancer. Several components cannot run in sidecar mode, including the Kubernetes Attributes Processor and Kubeletstats receiver.
  • Standalone Deployment (gateway): A central Collector service per cluster or region provides a place for credential management and filtering policy. Tail sampling also belongs there. The official docs give two warnings. Standard L4 load balancers pin persistent gRPC connections to a single backend, defeating horizontal scaling, so use an L7 gRPC-aware balancer. If you scale tail sampling across multiple gateways, you need a two-tier design where tier one runs the loadbalancingexporter with routing_key: traceID to fan spans out consistently to tier two.

A common production shape uses DaemonSet agents on every node to forward telemetry to a gateway Deployment. The gateway applies tail sampling and exports OTLP to Jaeger.

When Jaeger’s scope is too narrow: alternative OTLP-compatible backends

Because your instrumentation speaks OTLP, Jaeger is one destination among several, and switching costs are low. Choose among the candidates by signal coverage and storage model:

  • Grafana Tempo stores traces in object storage. Supported targets include S3 and GCS. Azure Blob is another option. Tempo accepts OTLP plus legacy Jaeger ingestion through Thrift and gRPC. It also accepts Zipkin ingestion, so existing Jaeger clients can point at Tempo unchanged, and its TraceQL query language went GA in Tempo 3.0. Metrics, logs, and dashboards come from the surrounding Grafana stack rather than Tempo itself.
  • SigNoz unifies traces, metrics, logs, and exceptions in one tool. SigNoz stores everything in ClickHouse, its primary and stable backend rather than an experimental option. It ingests OTLP on the standard ports and, via its bundled OTel Collector, Jaeger, Zipkin, Kafka, and OpenCensus formats.
  • Zipkin predates the OTLP era and shows it: no OTLP ingestion at all, only its native JSON and protobuf formats on port 9411. It matters mainly if you already run Zipkin.
  • Managed vendors: groundcover, Datadog, New Relic, Splunk, and Dynatrace all accept OTLP ingestion. Verify each vendor’s supported transport before standardizing on gRPC or HTTP. With the exception of groundcover (price by node) you trade operational burden for ingestion-priced billing.

eBPF as an alternative to SDK instrumentation

Everything above assumes you instrument each service with an OTel SDK, which for a platform team means coordinating with every application team across its services and languages. New workloads ship untraced until someone adds the SDK. eBPF removes that dependency: groundcover’s eBPF sensor deploys as a single DaemonSet, one pod per node. It captures traces, logs, metrics, and Kubernetes events directly from the Linux kernel.

groundcover ingests OTLP spans as a first-class data source, so services already using OTel SDK instrumentation coexist with kernel-captured traces in the same interface. ClickHouse stores traces and logs. It also stores events, while VictoriaMetrics stores metrics. Both systems run inside your own cloud account under BYOC, Bring Your Own Cloud. Deploy Flora as a DaemonSet on your own cluster and see every service without an instrumentation sprint.

Which should you choose?

You will run both, because they answer different questions. The decision splits by layer.

Use OpenTelemetry for:

  • Instrumenting applications, via SDKs or auto-instrumentation, in every language you run
  • Collecting and processing telemetry with the OTel Collector, including tail sampling and enrichment
  • Vendor-neutral OTLP export, so backends stay swappable config rather than code

Use Jaeger for:

  • Storing traces on a backend you operate (OpenSearch for scale, Badger for modest volumes)
  • Querying and visualizing traces and service maps. Jaeger also provides SPM-derived RED metrics
  • A CNCF-graduated, self-hosted trace backend with no license cost

If Jaeger’s traces-only scope leaves you assembling separate metrics and logging stacks, consider a unified OTLP backend instead. And if the SDK instrumentation burden itself is the problem, eBPF-based collection at the kernel level removes it without discarding the OTel spans you already produce.

FAQ

Do Jaeger and OpenTelemetry compete or complement each other? They complement each other: OTel instruments applications and ships the telemetry, Jaeger stores and displays the traces. Jaeger v2 itself uses the OTel Collector framework.

Should I run the OTel Collector or the Jaeger Collector? Run the OTel Collector. A Jaeger v2 deployment already embeds the Collector framework, and any additional agent or gateway tier should be standard OTel Collector instances.

Why were the Jaeger SDKs deprecated? Jaeger’s migration guidance directs users to the OTel SDKs. The maintainers froze the clients in January 2022 and archived the repositories; the OpenTracing shim covers existing code during migration.

Which Jaeger storage backend should I pick? OpenSearch, per the Jaeger team’s recommendation for large-scale production. Cassandra trades weaker search for native TTL, ClickHouse carries a schema-stability warning, and Badger or in-memory fit small setups.

Can Jaeger collect metrics or logs? No, it accepts traces only. Jaeger computes the Monitor tab’s RED metrics from span data, and Jaeger’s docs send you to the OTel Collector for real metrics and logs.

What are the OTLP-compatible alternatives to Jaeger? Grafana Tempo provides object storage and TraceQL. SigNoz stores all signals in ClickHouse. Managed options include Datadog, New Relic, Splunk, and Dynatrace. Zipkin has no OTLP support; groundcover takes OTLP spans alongside eBPF-captured telemetry.

How do I deploy both on Kubernetes? Install the Jaeger Helm chart and the opentelemetry-collector chart with mode set explicitly, then point the Collector’s OTLP exporter at the Jaeger service on port 4317. Tail sampling needs a gateway tier.

Sign up for Updates

Keep up with all things cloud-native observability.

We care about data. Check out our privacy policy.

Observability
for what comes next.

Start in minutes. No migrations. No data leaving your infrastructure. No surprises on the bill.