Skip to content

feat: add initial implementation for Java Observability Plugin - #381

Merged
Vadman97 merged 48 commits into
mainfrom
vadim/java-plugin
Oct 27, 2026
Merged

Vadman97 merged 48 commits into
mainfrom
vadim/java-plugin

Conversation

@Vadman97

@Vadman97 Vadman97 commented Feb 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Add the LaunchDarkly Observability plugin for the Java Server SDK.

Artifact: com.launchdarkly:launchdarkly-java-server-sdk-observability (follows the launchdarkly-java-server-sdk-otel / -redis-store / -ai family; the Android observability artifact is also a Java library, so a bare -java suffix was ambiguous).

The plugin wires OpenTelemetry traces, logs and metrics to OTLP/HTTP, registers the TracingHook for flag-evaluation spans, and applies backend sampling rules. LDObserve is a safe no-op before init and never installs the OpenTelemetry no-op global.

Options

enabled, serviceName, serviceVersion, environment, otlpEndpoint, backendUrl, resourceAttributes, customHeaders / customHeader, proxySelector / proxyHostAndPort, logsApiLevel (TRACE..FATAL, NONE), debug, manualStart.

Dependencies as a consumer sees them

  • opentelemetry-api is an api dependency (compile scope in the POM) because the public API takes Attributes and Severity.
  • OTLP uses opentelemetry-exporter-sender-jdk (java.net.http.HttpClient). No OkHttp, Okio or Kotlin stdlib reaches the consumer; :lib:check fails if any does, and fails if the POM scopes change.
  • The sampling request uses the same HttpClient, so proxies and custom headers behave identically for exports and the sampling fetch. With nothing configured the JVM default proxy selector applies.
  • The GraphQL sampling query is a compile-time constant, not a classpath resource.

CI

  • Java Observability: unit tests plus classpath and POM guards on JDK 11, 17 and 21; a consumer job publishes to Maven local and builds both e2e apps against the artifact through its POM.
  • Java Observability Plugin E2E: runs the self-checking example, builds the Spring Boot backend, drives every route over curl (including a recorded 500 and a clean SIGTERM shutdown), and builds its Dockerfile.
  • The dockerized e2e matrix (End to End Example Apps) gains a java service backed by the Spring Boot image.

e2e apps

  • e2e/java-plugin: PluginExample, a self-check against a local collector (flag span, sampling keep/drop, sampling ratio attribute, pre-init no-op).
  • e2e/java-plugin-spring-boot: a Spring Boot 3 web backend that uses the plugin the way a service does. One span, histogram, counter and log per request from a servlet filter; flag evaluations in every handler; /error recorded through LDObserve.recordError; /flags/{key}; actuator health. Honours LAUNCHDARKLY_SDK_KEY (offline when unset), OTEL_EXPORTER_OTLP_ENDPOINT, LD_OBSERVABILITY_BACKEND_URL, HTTPS_PROXY. This is the app to drive server-side feature work against.

Release

release-please versions the component (gradle.properties plus the version constant in Constants.java). On a release, release-please.yml runs the new publish-java-sdk action: :lib:build (tests and both guards), then publishToSonatype closeAndReleaseSonatypeStagingRepository, with the same Sonatype Central account and code-signing key as the Android plugin. manual-publish.yml has a Java toggle with dry-run.

How did you test this change?

  • ./gradlew :lib:build on JDK 11 and 21 (45 unit tests, runtime-classpath guard, POM guard).
  • make run and make consumer-check in e2e/java-plugin (composite build and Maven-local modes).
  • make test build in e2e/java-plugin-spring-boot, then the jar run and every route driven over curl; Docker image build and container health check.
  • ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository --dry-run with placeholder credentials to confirm the sign, publish, close and release task graph. The real upload is only exercised by the release workflow.

Are there any deployment considerations?

The Maven coordinates are permanent once published. Confirm launchdarkly-java-server-sdk-observability before the first release.

🤖 Generated with Claude Code

Opened from termag


Note

Overview
Introduces com.launchdarkly:launchdarkly-java-server-sdk-observability (0.1.0) — an OpenTelemetry-backed LaunchDarkly Server SDK plugin with flag-evaluation tracing, OTLP export, backend sampling, and the LDObserve API. The build enforces a JDK HTTP OTLP sender (no OkHttp/Kotlin on the runtime classpath) and POM guards for consumer-safe dependencies.

CI and release: Adds java-observability (unit tests on JDK 11/17/21, consumer checks) and java-plugin-e2e workflows, a publish-java-sdk composite action (Sonatype + signing, dry-run build), release-please/manual-publish hooks, and SLSA provenance. Docker e2e gains java-plugin-spring-boot with Python integration tests for logs, traces, and errors.

Examples: e2e/java-plugin (self-checking collector tests, including Java agent / claimed global) and e2e/java-plugin-spring-boot (Spring Boot 3 sample with request telemetry, routes, and javaagent smoke).

Reviewed by Cursor Bugbot for commit 59f581f. Bugbot is set up for automated code reviews on this repo. Configure here.

@Vadman97
Vadman97 requested a review from a team as a code owner February 19, 2026 20:22
@Vadman97 Vadman97 changed the title Vadim/java plugin feat: add initial implementation for Java Observability Plugin Feb 19, 2026
@Vadman97 Vadman97 changed the title feat: add initial implementation for Java Observability Plugin feat: add initial implementation for Java Observability Plugin Feb 19, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 5 potential issues.

@abelonogov-ld abelonogov-ld left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most files could be used, but client sdk semantics was transfered to server code, especially when multithreading occurs

@cursor

cursor Bot commented Oct 2, 2026

Copy link
Copy Markdown

Addressed the review on client-SDK semantics in server code. Provider startup is single-threaded (OtelManager.initialize takes one lock), the sampling query is loaded once and shared across threads, sampling config fetched before LDObserve.start is kept and applied, regexes are compiled once, and sampling uses ThreadLocalRandom. PluginUserFlowTest runs an offline LDClient with the plugin against a local OTLP and sampling server.

@Vadman97
Vadman97 requested a review from abelonogov-ld October 2, 2026 18:39

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@abelonogov-ld

Copy link
Copy Markdown
Contributor

The public API needs opentelemetry-api, but the POM publishes it as runtime, recordLog will not compile in some when used. The e2e example hides this by declaring opentelemetry-api explicitly, and the README doesn't mention it. Fix: declare opentelemetry-api as api.

@abelonogov-ld

Copy link
Copy Markdown
Contributor

Android moved forward and this version depends on many month behind Android code.

Proxy and custom HTTP settings are ignored. GraphQLClient uses a bare HttpURLConnection, and the OTLP exporters don't take headers or a proxy. Android already exposes customHeaders. Servers behind egress proxies will fail to fetch sampling config and to export.

@abelonogov-ld

Copy link
Copy Markdown
Contributor

ObservabilityOptions has no resourceAttributes, no log-level threshold, no enabled flag

@abelonogov-ld

Copy link
Copy Markdown
Contributor

okhttp, okio and the Kotlin stdlib leak unshaded. The default OTLP exporter brings okhttp 4.12, okio 3.6 and kotlin-stdlib 1.9.10. The LaunchDarkly server SDK itself shades its Kotlin and gson (com/launchdarkly/shaded/kotlin/... in its jar) precisely so server apps don't get version clashes. This plugin undoes that. The fix is cheap: OTel ships opentelemetry-exporter-sender-jdk, which uses Java 11's built-in HttpClient, and this module already targets Java 11.

@abelonogov-ld

Copy link
Copy Markdown
Contributor

The artifact name is ambiguous. The Android artifact is also a Java library, and LaunchDarkly's Java server family uses launchdarkly-java-server-sdk-* (-otel, -redis-store). Something with "server" in it would be clearer. Decide before the first publish, because Maven coordinates are permanent.

@abelonogov-ld

Copy link
Copy Markdown
Contributor

We need server CI configuration, many of above issues would be caught if we have one

@abelonogov-ld

Copy link
Copy Markdown
Contributor

Server e2e app wasn't provided.

Vadman97 and others added 7 commits October 19, 2026 20:01
Cache the sampling query, synchronize provider startup, keep sampling config across manual start, and attach launchdarkly.sampling.ratio on exported spans. Replace the Kotlin Gradle scripts with Groovy and drop the committed Gradle cache.

Co-authored-by: Vadim Korolik <vkorolik@launchdarkly.com>
The license link is the public Apache documentation endpoint, so it does not need to stay on plain HTTP.

Co-authored-by: Vadim Korolik <vkorolik@launchdarkly.com>
Pre-init LDObserve calls use a local no-op and no longer install GlobalOpenTelemetry, so TracingHook still exports after init. Sampling patterns are length-capped, allowlisted, and rejected when they can ReDoS. Exclude the Java SDK from the Yarn path filter.

Co-authored-by: Vadim Korolik <vkorolik@launchdarkly.com>
Closing an inner group now marks its parent, so ((a+))+ is dropped the same way as (a+)+ and is not compiled on the export path.

Co-authored-by: Vadim Korolik <vkorolik@launchdarkly.com>
The e2e app starts a local collector, registers ObservabilityPlugin, and exits non-zero unless the flag span, a kept span, and a dropped span arrive after a pre-init LDObserve call.

Co-authored-by: Vadim Korolik <vkorolik@launchdarkly.com>
Vadman97 and others added 8 commits October 22, 2026 03:08
Since OpenTelemetry 1.49 each OTLP exporter counts its exports into
GlobalOpenTelemetry's meter provider. Once the plugin registered as the
global, otlp.exporter.seen and otlp.exporter.exported were exported to
LaunchDarkly next to the application's metrics on every app we ran
(Spring Boot 3.5 and 4.1). The exporters now get a no-op meter
provider.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated-by: termag
A server that cannot reach the LaunchDarkly backend (offline, egress
blocked, wrong backendUrl) logged a WARNING with a full stack trace on
every start. It is now one line saying telemetry is exported unsampled,
with the stack trace at FINE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated-by: termag
With the startup fix a Spring Boot 3.2 app (OpenTelemetry 1.31) starts
and the plugin stays off, but the first LDObserve.recordMetric still
threw NoSuchMethodError from the no-op meter, because the synchronous
gauge API only exists since 1.38, and failed the request with a 500.
The call now swallows LinkageError; the plugin has already logged why
it is off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated-by: termag
Shutdown flushed each provider and then shut each down, waiting up to
10 s on every step in turn. With the OTLP endpoint unreachable a
Spring Boot app took 26 s to stop against a blackholed address and 9 s
against a refused port, close to a Kubernetes pod's default 30 s grace
period, which also has to cover Spring's request drain.

Shutdown now relies on provider shutdown exporting what it holds and
runs the three in parallel under one 5 s deadline; flush does the same
under 10 s. Each runs on its own daemon thread because the metric
reader's shutdown blocks before it returns its result code, so joining
the codes alone still took 10 s. Measured after: 5.5 s for both cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated-by: termag
…name

A working plugin logged nothing, so a new user could not tell from the
startup log whether it had started, which endpoint it exported to, or
that a missing serviceName had grouped everything under
unknown_service:java. The plugin now logs one INFO line with the
endpoint and resolved service.name, and a WARNING when no service name
is set (directly or through resourceAttributes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated-by: termag
… the README

Written from following the README into fresh Spring Boot 3.2 to 4.1
apps with Maven and Gradle:

- Spring Boot's BOM silently moves the plugin's OpenTelemetry 1.51 to
  1.31 (3.2, too old), 1.38, 1.43, 1.49 or 1.62. Document the 1.38
  minimum, the versions per Boot line, how to override
  opentelemetry.version, and keeping instrumentation libraries on the
  same core version (the Logback appender 2.17 on Boot 3.5 without the
  override hung requests with AbstractMethodError).
- A Spring Boot section: the LDClient bean, shutdownHook(false) with
  LDObserve.shutdown() from a bean, Micrometer tracing wired to
  LDObserve.getOpenTelemetry() for request spans and errors, and the
  Logback and Log4j 2 appenders for application logs. Each recipe was
  run against a local collector.
- What to set so the OpenTelemetry Java agent's request spans reach
  LaunchDarkly too.
- The Maven snippet left out launchdarkly-java-server-sdk, which the
  plugin does not bring, so a Maven build failed with "package
  com.launchdarkly.sdk.server does not exist".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated-by: termag
Generated-by: termag

# Conflicts:
#	.release-please-manifest.json
…K (7.10.1)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated-by: termag
Comment thread e2e/tests/src/conftest.py Dismissed
Comment thread e2e/tests/src/conftest.py Dismissed
Comment thread e2e/tests/src/conftest.py Dismissed
Comment thread e2e/tests/src/test_sdk.py Dismissed
Comment thread e2e/tests/src/test_sdk.py Dismissed
@Vadman97

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review. This round's replies to the top-level comments are below; I also replied on each open thread. In short: 7 fixes from a fresh audit of the feedback, plus 9 from end-to-end testing of new Spring Boot apps (3.2–4.1, Maven and Gradle, JDK 11/17/21).

End-to-end Spring Boot testing (new in this round)

  • 274fc0a2: the app keeps running when the plugin can't start. On Spring Boot 3.2, Boot pins OTel 1.31 (older than the plugin's floor), and the app used to fail to boot. Now the plugin logs one line and stays off.
  • 04bd6bbf: new shutdownHook(boolean) option (default true). JVM shutdown hooks run concurrently, so under Spring the plugin's hook stopped exporting while Spring was still draining requests, and their telemetry was lost. With shutdownHook(false), the app calls LDObserve.shutdown() from a bean, after the drain.
  • 27436314: new LDObserve.getOpenTelemetry(). The plugin creates no request spans, so without this every flag evaluation is its own root trace and SLF4J logs are never exported. GlobalOpenTelemetry.get() is the wrong instance whenever the global is already claimed. The returned view resolves the providers on each call, is safe to take before start, and has no close() (so Spring can't infer a destroy method that stops the providers).
  • 26981375: the exporters' own otlp.exporter.* counters stay out of customer metrics. 0b0fc990: an unreachable sampling backend is one log line, not a stack trace. 253ce922: recordMetric is a no-op on OTel < 1.38 instead of throwing. 0a58be12: shutdown takes at most 5 s against an unreachable endpoint (it took 26 s). 2cac5218: one startup line saying where telemetry goes, plus a warning when no service name is set.
  • 79afcf98: README sections for Spring Boot (OTel version per Boot line, wiring Spring tracing and log appenders) and the Java agent.

The two new public APIs, getOpenTelemetry() and shutdownHook, are the main things I'd like your opinion on.

Review 3939255315 (CHANGES_REQUESTED, 2026-03-12): "client sdk semantics transferred to server code, especially when multithreading occurs"

I went through the whole module again with server semantics in mind (thread-safety, lifecycle, hot paths, classpath). On top of the earlier fixes (single-lock init, compile-time query, sampling config kept across manual start, regexes compiled once, ThreadLocalRandom), this round fixes:

  • Hot paths never throw. LDObserve threw NPEs into request threads on a null metric name, null attributes or null severity (3225375). Log sampling runs on the caller's thread and could throw StackOverflowError from a backend regex. Log and span sampling now fail open (2a43c1d).
  • Multiple LDClients. The providers are process-wide and stamp one SDK key. A second client for another environment used to replace the first client's sampling rules. Now the first key owns the providers; a client with another key logs a warning and its plugin starts nothing, while its evaluations still record on the shared providers (5f24da7).
  • Lifecycle. One JVM shutdown hook per process, not one per restart, and it no longer races LDObserve.shutdown() into a double shutdown (5f24da7). The README documents that closing LDClient does not stop the providers (the server SDK's Plugin has no close callback) and that LDObserve.shutdown() or the hook flushes them.
  • Classpath. The POM now carries only io.opentelemetry artifacts: Gson is gone (JSON goes through the SDK's own LDValue, abb3e08), and checkPublishedPom fails on anything else, LaunchDarkly artifacts included (9d2e9d0).
  • Sampling parity with Go/.NET. Typed value matching (ca8e984), on top of substring regex and compile-as-sent (d097278).
    Thread-safety as it stands: provider state sits behind an AtomicReference plus one init lock; instrument caches are ConcurrentHashMaps; the sampling config is immutable and published through an AtomicReference; GraphQLClient and the hook are stateless or immutable. Unit tests pass on JDK 11, 17 and 21 (117 tests), and also against SDK 7.10.1.

Review 5581811529 (2026-10-20): launchdarkly-java-server-sdk:7.4.0 pulled in by -otel:0.2.0

Fixed. b5c6e22 dropped the launchdarkly-java-server-sdk-otel dependency (the plugin has its own EvaluationTracingHook), so nothing pulls 7.4.0 any more. 9d2e9d0 stops it coming back: checkPublishedPom fails if the POM names any com.launchdarkly artifact, and since abb3e08 anything outside io.opentelemetry. The SDK stays compileOnly, so the application's own SDK is the only copy. I checked Maven Central: Plugin first shipped in 7.10.1 (7.9.x has Hook but no Plugin), so the README now says "7.10.1+" and warns about the 7.4.0 that -otel 0.2.0 brings. ./gradlew :lib:test -PldServerSdkVersion=7.10.1 passes on JDK 11. A CI step for that floor is proposed in the handoff; workflow edits are applied separately.

Issue comment 6294816372 (2026-10-20): missing server tests

  • Shared scenarios: 8831f28 runs span-test-scenarios.json / log-test-scenarios.json (copied from go/internal/otel) through the real parser (SamplingScenariosTest). :lib:checkSamplingFixtures fails the build if the copies drift from Go's. They found a real bug: severityText was matched against the enum name.
  • CustomSampler.sampleLog: direct tests in CustomSamplerTest (sampleLogWithoutConfigAlwaysSamples, ...MatchesSeverityTextField, ...MatchesMessageByValueAndSubstringRegex, ...MatchesTypedAttributes, ...FirstMatchingRuleWins), plus SamplingFailureTest for the fail-open path.
  • Spring Boot runtime tests: c440e6b adds e2e/java-plugin-spring-boot/src/test/.../ApplicationTelemetryTest. It boots the whole app against a local OTLP collector and sampling endpoint, drives every route and checks spans, flag-evaluation spans and events, the recorded error, logs with severity, metrics, resource attributes, and a backend sampling rule. It runs in check, so make test and the CI consumer job run it.
  • e2e/tests/src/test_sdk.py: b420dc4 adds a java_app fixture (publishes the plugin to Maven local, builds and runs the Spring Boot jar as project 1) and test_java_logs, test_java_traces (request span with its child flag-evaluation span) and test_java_error, in the same shape as the .NET cases. Like the rest of that file they need a local backend; I collected them but have not run them against one.

Issue comment 6007495398 (2026-10-06): opentelemetry-api runtime scope

Confirmed fixed: api "io.opentelemetry:opentelemetry-api" (lib/build.gradle), compile scope in the generated POM, enforced by checkPublishedPom in check. The Spring Boot example compiles without declaring the OTel API itself.

Issue comment 6007511421 (2026-10-06): Android drift; proxy and custom HTTP settings ignored

Proxy/headers: confirmed. customHeaders/customHeader and proxySelector/proxyHostAndPort apply to all three OTLP exporters (OtelManager.java:169-219) and to the sampling request (GraphQLClient, java.net.http.HttpClient, which uses the JVM default selector when none is set). Covered by PluginOptionsFlowTest.proxySelectorRoutesExportsAndSamplingRequestThroughProxy and GraphQLClientTest.proxySelectorRoutesRequestThroughProxy.
Android drift: the server plugin no longer tracks Android's code. Sampling now follows Go/.NET semantics and is checked against the shared scenarios, and the server-relevant options Android has (enabled, resourceAttributes, customHeaders, logsApiLevel) are here. Android's tracesApi/metricsApi switches are not. Do you want those on the server too, or is enabled plus logsApiLevel enough?

Issue comment 6007531470 (2026-10-06): no resourceAttributes / log-level / enabled

Confirmed fixed in ef0f4e3: enabled (no providers, no hook, no sampling fetch, LDObserve no-op), resourceAttributes (merged last so they can override defaults) and logsApiLevel with NONE. Covered by PluginOptionsFlowTest.

Issue comment 6008278745 (2026-10-06): okhttp/okio/kotlin leak

Confirmed fixed (4342f83): the OkHttp sender is excluded and opentelemetry-exporter-sender-jdk used, and checkRuntimeClasspath fails on OkHttp, Okio or Kotlin. Going further, abb3e08 removed Gson too, so the runtime classpath is now only io.opentelemetry:* (12 artifacts, all 1.51.0).

Issue comment 6008281249 (2026-10-06): artifact name

The artifact is com.launchdarkly:launchdarkly-java-server-sdk-observability, following -otel / -redis-store / -ai. That is our pick, not a confirmed decision. Can you (or the SDK team) confirm before the first release? Maven coordinates are permanent.

Issue comment 6008870343 (2026-10-06): server CI

Confirmed: java-observability.yml runs :lib:build (tests plus classpath, POM and fixture guards) on JDK 11/17/21. A consumer job publishes to Maven local and builds both e2e apps through the POM; the Spring Boot check boots the app against a local collector. java-plugin-e2e.yml covers the Docker image, SIGTERM and the Java agent.

Issue comment 6008927803 (2026-10-06): server e2e app

Confirmed: e2e/java-plugin-spring-boot (Spring Boot 3.5 web backend) is in the dockerized e2e matrix, has runtime tests (ApplicationTelemetryTest), and since b420dc4 is in e2e/tests/src/test_sdk.py.

Open questions

  1. Artifact name com.launchdarkly:launchdarkly-java-server-sdk-observability: OK before the first publish?
  2. Should Android's tracesApi/metricsApi switches come to the server plugin, or are enabled + logsApiLevel enough?
  3. With LDClient offline(true), should the plugin also go idle? It still exports today.
  4. Several LDClients with different SDK keys: is "first key owns the providers, later keys warn" acceptable for v0.1, with per-client providers as a follow-up?
  5. Spring Boot setup is still about 20 lines (bean, shutdown wiring, log appender). I'd do an optional auto-configuration module as a follow-up PR rather than grow this one. Agreed?

@abelonogov-ld

Copy link
Copy Markdown
Contributor

No sampling fetch in standalone mode.

@abelonogov-ld

Copy link
Copy Markdown
Contributor

it still has no package-hashes output and no provenance job, unlike Python, .NET and Ruby.

Vadman97 and others added 3 commits October 26, 2026 21:33
…Error

span.recordException already records the stack trace on its exception event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated-by: termag
…he plugin

The fetch now lives in OtelManager.fetchSamplingConfig and runs once per SDK
key, so plugin registration and LDObserve.start share it: standalone use is
sampled, and plugin + manualStart still fetches once. A key other than the
registered client's does not replace that client's config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated-by: termag
The publish action hashes the released jars, and release-please and manual
publish run the SLSA generator on them, as for Python, .NET and Ruby. The jars
are named after the Maven artifactId so the provenance subjects match Maven
Central.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated-by: termag
@Vadman97

Copy link
Copy Markdown
Contributor Author

Follow-ups on the two top-level comments from 10/23:

  • No sampling fetch in standalone mode. Fixed in e75b5a7. The background fetch moved from ObservabilityPlugin.register to OtelManager.fetchSamplingConfig(sdkKey, options). LDObserve.start calls it, so using LDObserve without an LDClient now gets the backend's sampling rules. It runs once per SDK key, so plugin + manualStart(true) still fetches only once, and a key other than the registered client's can't replace that client's config. Tests: PluginUserFlowTest.standaloneStartFetchesAndAppliesSamplingConfig, and manualStartKeepsSamplingConfigFetchedBeforeProvidersStart now also asserts there is a single fetch.
  • No package-hashes output and no provenance job. Fixed in ac160d1. publish-java-sdk now outputs package-hashes (SHA-256 of the main, sources and javadoc jars). release-please.yml (release-java-provenance) and manual-publish.yml (publish-java-sdk-provenance, plus a new java_tag_name input) run slsa-github-generator on those hashes, the same way as Python, .NET and Ruby. The jars are now named after the artifactId (launchdarkly-java-server-sdk-observability-<version>*.jar), so the provenance subjects match the files on Maven Central. The POM and coordinates are unchanged.

Comment thread e2e/java-plugin/build.gradle Resolved
Vadman97 and others added 2 commits October 27, 2026 17:57
Like the other server SDKs, recordError adds the exception event, with the
caller's attributes, to the current span instead of starting a separate
highlight.error span. A span from another provider (the Java agent or the
application's own SDK) would not reach LaunchDarkly, so there the error is
still a new error span, a child of the current one in the same trace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated-by: termag
…ility

--release checks API use against the target JDK, so a build on JDK 17 or 21
cannot call an API that Java 11 lacks. The published module metadata still
declares Java 11.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated-by: termag
@Vadman97
Vadman97 merged commit 9f458f0 into main Oct 27, 2026
32 checks passed
@Vadman97
Vadman97 deleted the vadim/java-plugin branch October 27, 2026 22:01
Vadman97 added a commit that referenced this pull request Oct 27, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>launchdarkly-java-server-sdk-observability:
0.1.0</summary>

##
[0.1.0](https://github.com/launchdarkly/observability-sdk/releases/tag/launchdarkly-java-server-sdk-observability-0.1.0)
(2026-10-27)


### Features

* add initial implementation for Java Observability Plugin
([#381](#381))
([9f458f0](9f458f0))

</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> **Release Please** adds
`sdk/@launchdarkly/observability-java/CHANGELOG.md` to publish
**`launchdarkly-java-server-sdk-observability` 0.1.0** (2026-10-27).
> 
> The new changelog entry records the first public release, including
the initial Java Observability Plugin work ([#381]) and the release
chore ([#852]); it also lists an unrelated React Native session-replay
note that Release Please rolled into this package’s notes.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
2eb2c22. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Vadim Korolik <vkorolik@launchdarkly.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants