open-telemetry / open-telemetry/opentelemetry-java-instrumentation

Should declarative config be the default in Java Agent 3.0?

Open
#16,317 5 comments 1 reaction 1 assignee View on GitHub

@zeitlinger is already working on this.

Since Feb 27, 2026.

declarative configuration
Dominant language
Java
Stars
2.6k
Forks
1.2k
Avg merge
2d 18h
Merged PRs (30d)
228

Description

Context

Java Agent 3.0 is a major version bump — breaking changes are acceptable. Declarative config (DC) is the future of OTel configuration: more expressive, structured, and aligns with the spec direction. The question is what role DC plays by default in 3.0.

Key spec behavior: when DC is active, all env vars are ignored except those referenced via ${...} substitution in the YAML file (spec link).

Prior art: the OTel Collector requires an explicit --config flag — no auto-discovery, no built-in default.

Decision tree
flowchart TD
    Q1{{"Q1: Should DC be the default\nconfiguration mode in 3.0?"}}

    Q1 -->|YES| Q2{{"Q2: Provide a built-in default config\nwhen user provides no settings?"}}
    Q1 -->|NO| A["A: DC remains opt-in (status quo).\nEnv vars / system properties stay primary.\nUsers opt into DC via OTEL_CONFIG_FILE."]

    Q2 -->|YES| Q4{{"Q4: Should built-in default\npreserve env var compat?"}}
    Q2 -->|NO| Q3{{"Q3: Look for a config file\nat a default path?"}}

    Q4 -->|YES| B["B: Built-in = migration config.\nAll mappable OTEL_* env vars\nreferenced via substitution.\nMax env var compat."]
    Q4 -->|NO| Q5{{"Q5: How minimal should\nthe built-in default be?"}}

    Q5 -->|minimal| E["E: Built-in = getting-started config.\nOnly OTEL_EXPORTER_OTLP_ENDPOINT\n+ OTEL_RESOURCE_ATTRIBUTES."]
    Q5 -->|hardcoded| F["F: Built-in = sdk-config.\nAll values hardcoded, no env vars.\nFull DC, clean break."]

    Q3 -->|YES| C["C: Auto-discover\n(e.g. otel-config.yaml in working dir)\nbefore erroring"]
    Q3 -->|NO| D["D: Require explicit OTEL_CONFIG_FILE.\nError on startup if not provided.\n(Collector model)"]

    B -.- REC["⭐ Proposed"]
Pros and cons per outcome
Q1: Should DC be the default configuration mode in 3.0?
DC is default DC stays opt-in
DC is the future: more expressive, structured, spec-aligned No migration effort — env vars work exactly as before
Users are already on the DC path — adding a YAML file is incremental, not a mode switch Users hit the on-ramp cliff later when they opt in
Builds DC ecosystem momentum — more users means more tooling, examples, and bug reports
⚠️ With migration config: most env vars keep working, but some structurally unmappable env vars silently break on upgrade
Q2: Provide a built-in default config when user provides no settings?
Built-in default No built-in default
Zero-config still works — agent is useful out of the box No built-in config to maintain
Users are on DC without knowing it — but gain no new functionality until they write YAML Forces explicit opt-in, which makes the on-ramp cliff a conscious choice
Q3: Look for a config file at a default path?
Auto-discover Require explicit path
Less setup — drop a file in a known location, done No surprising behavior
Q4: Should built-in default preserve env var compat as much as possible?
Max compat Clean break
Most env vars keep working via ${...} substitution — smooth migration No partial compat — avoids confusion about which env vars work and which don't
Smooth on-ramp to DC Built-in config is simpler to maintain
Q5: Should the built-in default reference any env vars at all?
Some env vars No env vars
Keeps the most important env vars working (OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_RESOURCE_ATTRIBUTES) Cleanest break — no env var ambiguity, DC is the only way
Encourages YAML adoption for anything beyond basics Simplest to maintain (no substitution references)
Must provide custom file to change anything (endpoint, service name, etc.)
How env vars work under each outcome

Per spec, when DC is active all OTEL_* env vars are ignored except those referenced via ${...} substitution in the YAML file (spec link).

The three example configs from the spec repo represent different points on the env var compatibility spectrum:

Outcome Built-in content Env vars that work Env vars that break
B (migration) otel-sdk-migration-config.yaml Most: OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_BSP_*, OTEL_RESOURCE_ATTRIBUTES, limits, etc. OTEL_TRACES_EXPORTER, OTEL_TRACES_SAMPLER, OTEL_LOG_LEVEL, protocol selection (full list in file header)
E (getting-started) otel-getting-started.yaml OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_RESOURCE_ATTRIBUTES, OTEL_SERVICE_NAME (via detector) Everything else
F (sdk-config) otel-sdk-config.yaml None All — clean break

Note: even with B (migration), some env vars don't map cleanly to DC's hierarchical structure (e.g. OTEL_TRACES_EXPORTER selects between exporter types, but in DC you configure a specific exporter directly). These are explicitly listed in the migration config's header.

When a user provides their own custom YAML file (overriding any built-in default), only the ${...} references in their file determine which env vars work. Unreferenced vars are silently ignored — this is the main gotcha to document.

Proposal

Q1=YES → Q2=YES → Q4=YES (outcome B). Reasoning:

  • momentum: Q1=YES — more users on DC means more tooling, examples, and bug reports
  • zero-config: Q2=YES — agent is useful out of the box, no file required
  • easy migration: Q4=YES — most existing env var setups keep working
  • ⚠️ Requires startup warnings for env vars that are set but ignored under DC (see on-ramp cliff below)
The DC on-ramp cliff

Regardless of Q1 (whether DC is default or opt-in), every user who adopts DC hits the same problem: the migration config is the best possible env var compatibility layer, but it has structural gaps that cannot be fixed.

Scenario: A user on env vars discovers a DC-only feature (e.g. complex sampler chain, extended methods config, structured instrumentation rules). They start from the migration config and add their feature. But some env vars they relied on silently stop working because the migration config can't map them.

Highest-risk unmapped env vars (structural — cannot be added to migration config):

Env var Why unmappable Impact if silently ignored
OTEL_TRACES_SAMPLER + _ARG DC uses typed sampler blocks, not string selectors User with traceidratio:0.1 gets 100% sampling — cost/perf bomb
OTEL_TRACES_EXPORTER / METRICS / LOGS DC uses typed exporter blocks User who set none to disable a signal gets it re-enabled
OTEL_EXPORTER_OTLP_PROTOCOL Migration config hardcodes exporter type gRPC users silently switch to HTTP/protobuf
OTEL_INSTRUMENTATION_[NAME]_ENABLED Agent-specific, DC instrumentation section doesn't support per-library enable/disable yet Disabled instrumentations reactivate
OTEL_JAVAAGENT_EXTENSIONS Not a DC concept Extensions silently stop loading
OTEL_EXPORTER_OTLP_INSECURE No DC equivalent — uses endpoint scheme gRPC plaintext connections hit TLS errors

Note: generic OTLP env vars (OTEL_EXPORTER_OTLP_HEADERS, _CERTIFICATE, _TIMEOUT) are also not mapped — the migration config only maps per-signal variants. Users who set the generic form lose their settings.

This cliff exists regardless of whether DC is default (outcome B) or opt-in (outcome A). The difference is only when users hit it — on upgrade (B) or when they opt in (A).

Proposed mitigation: startup warnings. When DC is active, the agent should detect OTEL_* env vars that are set but not referenced in the active config file, and emit a warning:

WARN io.opentelemetry.javaagent - Declarative config is active. The following env vars are set but ignored
(not referenced in the config file): OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG.
See https://opentelemetry.io/docs/zero-code/java/agent/configuration/#declarative-config for how to configure these in YAML.

This doesn't change the proposal (B is still the best option), but it's a required companion to any DC adoption path — without it, users will silently lose configuration they depend on.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.