From dc8d31969b658d75e4978f27737770c01a310af5 Mon Sep 17 00:00:00 2001 From: Giannis Gkiortzis <58184179+giortzisg@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:10:30 +0100 Subject: [PATCH 1/8] feat: Add strict trace continuation support Extract org ID from DSN host, add strictTraceContinuation and orgId options, propagate sentry-org_id in baggage, and validate incoming traces per the decision matrix. Closes #5128 --- sentry/src/main/java/io/sentry/Baggage.java | 17 +++- sentry/src/main/java/io/sentry/Dsn.java | 22 +++++ .../java/io/sentry/PropagationContext.java | 35 +++++++ sentry/src/main/java/io/sentry/Scopes.java | 2 +- .../main/java/io/sentry/SentryOptions.java | 46 +++++++++ sentry/src/test/java/io/sentry/DsnTest.kt | 32 ++++++ .../java/io/sentry/PropagationContextTest.kt | 97 +++++++++++++++++++ 7 files changed, 249 insertions(+), 2 deletions(-) diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 5f610a02918..4645df3f3a4 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -186,6 +186,7 @@ public static Baggage fromEvent( baggage.setPublicKey(options.retrieveParsedDsn().getPublicKey()); baggage.setRelease(event.getRelease()); baggage.setEnvironment(event.getEnvironment()); + baggage.setOrgId(options.getEffectiveOrgId()); baggage.setTransaction(transaction); // we don't persist sample rate baggage.setSampleRate(null); @@ -450,6 +451,16 @@ public void setReplayId(final @Nullable String replayId) { set(DSCKeys.REPLAY_ID, replayId); } + @ApiStatus.Internal + public @Nullable String getOrgId() { + return get(DSCKeys.ORG_ID); + } + + @ApiStatus.Internal + public void setOrgId(final @Nullable String orgId) { + set(DSCKeys.ORG_ID, orgId); + } + /** * Sets / updates a value, but only if the baggage is still mutable. * @@ -501,6 +512,7 @@ public void setValuesFromTransaction( if (replayId != null && !SentryId.EMPTY_ID.equals(replayId)) { setReplayId(replayId.toString()); } + setOrgId(sentryOptions.getEffectiveOrgId()); setSampleRate(sampleRate(samplingDecision)); setSampled(StringUtils.toString(sampled(samplingDecision))); setSampleRand(sampleRand(samplingDecision)); @@ -536,6 +548,7 @@ public void setValuesFromScope( if (!SentryId.EMPTY_ID.equals(replayId)) { setReplayId(replayId.toString()); } + setOrgId(options.getEffectiveOrgId()); setTransaction(null); setSampleRate(null); setSampled(null); @@ -632,6 +645,7 @@ public static final class DSCKeys { public static final String SAMPLE_RAND = "sentry-sample_rand"; public static final String SAMPLED = "sentry-sampled"; public static final String REPLAY_ID = "sentry-replay_id"; + public static final String ORG_ID = "sentry-org_id"; public static final List ALL = Arrays.asList( @@ -644,6 +658,7 @@ public static final class DSCKeys { SAMPLE_RATE, SAMPLE_RAND, SAMPLED, - REPLAY_ID); + REPLAY_ID, + ORG_ID); } } diff --git a/sentry/src/main/java/io/sentry/Dsn.java b/sentry/src/main/java/io/sentry/Dsn.java index 705d383266e..e28e831848e 100644 --- a/sentry/src/main/java/io/sentry/Dsn.java +++ b/sentry/src/main/java/io/sentry/Dsn.java @@ -2,15 +2,20 @@ import io.sentry.util.Objects; import java.net.URI; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; final class Dsn { + private static final @NotNull Pattern ORG_ID_PATTERN = Pattern.compile("^o(\\d+)\\."); + private final @NotNull String projectId; private final @Nullable String path; private final @Nullable String secretKey; private final @NotNull String publicKey; private final @NotNull URI sentryUri; + private @Nullable String orgId; /* / The project ID which the authenticated user is bound to. @@ -87,8 +92,25 @@ URI getSentryUri() { sentryUri = new URI( scheme, null, uri.getHost(), uri.getPort(), path + "api/" + projectId, null, null); + + // Extract org ID from host (e.g., "o123.ingest.sentry.io" -> "123") + final String host = uri.getHost(); + if (host != null) { + final Matcher matcher = ORG_ID_PATTERN.matcher(host); + if (matcher.find()) { + orgId = matcher.group(1); + } + } } catch (Throwable e) { throw new IllegalArgumentException(e); } } + + public @Nullable String getOrgId() { + return orgId; + } + + void setOrgId(final @Nullable String orgId) { + this.orgId = orgId; + } } diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index e7d39d35fe5..772013ec567 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -23,6 +23,14 @@ public static PropagationContext fromHeaders( final @NotNull ILogger logger, final @Nullable String sentryTraceHeaderString, final @Nullable List baggageHeaderStrings) { + return fromHeaders(logger, sentryTraceHeaderString, baggageHeaderStrings, null); + } + + public static @NotNull PropagationContext fromHeaders( + final @NotNull ILogger logger, + final @Nullable String sentryTraceHeaderString, + final @Nullable List baggageHeaderStrings, + final @Nullable SentryOptions options) { if (sentryTraceHeaderString == null) { return new PropagationContext(); } @@ -30,6 +38,12 @@ public static PropagationContext fromHeaders( try { final @NotNull SentryTraceHeader traceHeader = new SentryTraceHeader(sentryTraceHeaderString); final @NotNull Baggage baggage = Baggage.fromHeader(baggageHeaderStrings, logger); + + if (options != null && !shouldContinueTrace(options, baggage)) { + logger.log(SentryLevel.DEBUG, "Not continuing trace due to org ID mismatch."); + return new PropagationContext(); + } + return fromHeaders(traceHeader, baggage, null); } catch (InvalidSentryTraceHeaderException e) { logger.log(SentryLevel.DEBUG, e, "Failed to parse Sentry trace header: %s", e.getMessage()); @@ -149,4 +163,25 @@ public void setSampled(final @Nullable Boolean sampled) { // should never be null since we ensure it in ctor return sampleRand == null ? 0.0 : sampleRand; } + + static boolean shouldContinueTrace( + final @NotNull SentryOptions options, final @Nullable Baggage baggage) { + final @Nullable String sdkOrgId = options.getEffectiveOrgId(); + final @Nullable String baggageOrgId = baggage != null ? baggage.getOrgId() : null; + + // Mismatched org IDs always reject regardless of strict mode + if (sdkOrgId != null && baggageOrgId != null && !sdkOrgId.equals(baggageOrgId)) { + return false; + } + + // In strict mode, both must be present and match (unless both are missing) + if (options.isStrictTraceContinuation()) { + if (sdkOrgId == null && baggageOrgId == null) { + return true; + } + return sdkOrgId != null && sdkOrgId.equals(baggageOrgId); + } + + return true; + } } diff --git a/sentry/src/main/java/io/sentry/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index e155979e064..4fc1e61c271 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -1135,7 +1135,7 @@ public void reportFullyDisplayed() { final @Nullable String sentryTrace, final @Nullable List baggageHeaders) { @NotNull PropagationContext propagationContext = - PropagationContext.fromHeaders(getOptions().getLogger(), sentryTrace, baggageHeaders); + PropagationContext.fromHeaders(getOptions().getLogger(), sentryTrace, baggageHeaders, getOptions()); configureScope( (scope) -> { scope.withPropagationContext( diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 7883ed6b95b..9457a369713 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -432,6 +432,21 @@ public class SentryOptions { /** Whether to propagate W3C traceparent HTTP header. */ private boolean propagateTraceparent = false; + /** + * Controls whether the SDK requires matching org IDs from incoming baggage to continue a trace. + * When true, both the SDK's org ID and the incoming baggage org ID must be present and match. + * When false, a mismatch between present org IDs will still start a new trace, but missing org + * IDs on either side are tolerated. + */ + private boolean strictTraceContinuation = false; + + /** + * An optional organization ID. The SDK will try to extract it from the DSN in most cases but you + * can provide it explicitly for self-hosted and Relay setups. This value is used for trace + * propagation and for features like {@link #strictTraceContinuation}. + */ + private @Nullable String orgId; + /** Proguard UUID. */ private @Nullable String proguardUuid; @@ -2301,6 +2316,37 @@ public void setPropagateTraceparent(final boolean propagateTraceparent) { this.propagateTraceparent = propagateTraceparent; } + public boolean isStrictTraceContinuation() { + return strictTraceContinuation; + } + + public void setStrictTraceContinuation(final boolean strictTraceContinuation) { + this.strictTraceContinuation = strictTraceContinuation; + } + + public @Nullable String getOrgId() { + return orgId; + } + + public void setOrgId(final @Nullable String orgId) { + this.orgId = orgId; + } + + /** + * Returns the effective org ID, preferring the explicit config option over the DSN-parsed value. + */ + public @Nullable String getEffectiveOrgId() { + if (orgId != null) { + return orgId; + } + try { + final @Nullable String dsnOrgId = retrieveParsedDsn().getOrgId(); + return dsnOrgId; + } catch (Throwable e) { + return null; + } + } + /** * Returns a Proguard UUID. * diff --git a/sentry/src/test/java/io/sentry/DsnTest.kt b/sentry/src/test/java/io/sentry/DsnTest.kt index 6c454ad5c75..29f70229202 100644 --- a/sentry/src/test/java/io/sentry/DsnTest.kt +++ b/sentry/src/test/java/io/sentry/DsnTest.kt @@ -121,4 +121,36 @@ class DsnTest { Dsn("HTTP://publicKey:secretKey@host/path/id") Dsn("HTTPS://publicKey:secretKey@host/path/id") } + + @Test + fun `extracts org id from host`() { + val dsn = Dsn("https://key@o123.ingest.sentry.io/456") + assertEquals("123", dsn.orgId) + } + + @Test + fun `extracts single digit org id from host`() { + val dsn = Dsn("https://key@o1.ingest.us.sentry.io/456") + assertEquals("1", dsn.orgId) + } + + @Test + fun `returns null org id when host has no org prefix`() { + val dsn = Dsn("https://key@sentry.io/456") + assertNull(dsn.orgId) + } + + @Test + fun `returns null org id for non-standard host`() { + val dsn = Dsn("http://key@localhost:9000/456") + assertNull(dsn.orgId) + } + + @Test + fun `org id can be overridden via setter`() { + val dsn = Dsn("https://key@o123.ingest.sentry.io/456") + assertEquals("123", dsn.orgId) + dsn.setOrgId("999") + assertEquals("999", dsn.orgId) + } } diff --git a/sentry/src/test/java/io/sentry/PropagationContextTest.kt b/sentry/src/test/java/io/sentry/PropagationContextTest.kt index 8e83dec4deb..3431a88f37a 100644 --- a/sentry/src/test/java/io/sentry/PropagationContextTest.kt +++ b/sentry/src/test/java/io/sentry/PropagationContextTest.kt @@ -1,7 +1,9 @@ package io.sentry import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertNotEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -42,4 +44,99 @@ class PropagationContextTest { assertTrue(propagationContext.baggage.isMutable) assertFalse(propagationContext.baggage.isShouldFreeze) } + + // Decision matrix tests for shouldContinueTrace + + private val incomingTraceId = "bc6d53f15eb88f4320054569b8c553d4" + private val sentryTrace = "bc6d53f15eb88f4320054569b8c553d4-b72fa28504b07285-1" + + private fun makeOptions(dsnOrgId: String?, explicitOrgId: String? = null, strict: Boolean = false): SentryOptions { + val options = SentryOptions() + if (dsnOrgId != null) { + options.dsn = "https://key@o$dsnOrgId.ingest.sentry.io/123" + } else { + options.dsn = "https://key@sentry.io/123" + } + options.orgId = explicitOrgId + options.isStrictTraceContinuation = strict + return options + } + + private fun makeBaggage(orgId: String?): String { + val parts = mutableListOf("sentry-trace_id=$incomingTraceId") + if (orgId != null) { + parts.add("sentry-org_id=$orgId") + } + return parts.joinToString(",") + } + + @Test + fun `strict=false, matching orgs - continues trace`() { + val options = makeOptions(dsnOrgId = "1", strict = false) + val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage("1"), options) + assertEquals(incomingTraceId, pc.traceId.toString()) + } + + @Test + fun `strict=false, baggage missing org - continues trace`() { + val options = makeOptions(dsnOrgId = "1", strict = false) + val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage(null), options) + assertEquals(incomingTraceId, pc.traceId.toString()) + } + + @Test + fun `strict=false, sdk missing org - continues trace`() { + val options = makeOptions(dsnOrgId = null, strict = false) + val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage("1"), options) + assertEquals(incomingTraceId, pc.traceId.toString()) + } + + @Test + fun `strict=false, both missing org - continues trace`() { + val options = makeOptions(dsnOrgId = null, strict = false) + val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage(null), options) + assertEquals(incomingTraceId, pc.traceId.toString()) + } + + @Test + fun `strict=false, mismatched orgs - starts new trace`() { + val options = makeOptions(dsnOrgId = "2", strict = false) + val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage("1"), options) + assertNotEquals(incomingTraceId, pc.traceId.toString()) + } + + @Test + fun `strict=true, matching orgs - continues trace`() { + val options = makeOptions(dsnOrgId = "1", strict = true) + val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage("1"), options) + assertEquals(incomingTraceId, pc.traceId.toString()) + } + + @Test + fun `strict=true, baggage missing org - starts new trace`() { + val options = makeOptions(dsnOrgId = "1", strict = true) + val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage(null), options) + assertNotEquals(incomingTraceId, pc.traceId.toString()) + } + + @Test + fun `strict=true, sdk missing org - starts new trace`() { + val options = makeOptions(dsnOrgId = null, strict = true) + val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage("1"), options) + assertNotEquals(incomingTraceId, pc.traceId.toString()) + } + + @Test + fun `strict=true, both missing org - continues trace`() { + val options = makeOptions(dsnOrgId = null, strict = true) + val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage(null), options) + assertEquals(incomingTraceId, pc.traceId.toString()) + } + + @Test + fun `strict=true, mismatched orgs - starts new trace`() { + val options = makeOptions(dsnOrgId = "2", strict = true) + val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage("1"), options) + assertNotEquals(incomingTraceId, pc.traceId.toString()) + } } From e550ae3e13a1b70a3332b8f616dcab13fb136678 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Mon, 2 Mar 2026 15:17:51 +0000 Subject: [PATCH 2/8] Format code --- sentry/src/main/java/io/sentry/Scopes.java | 3 +- .../java/io/sentry/PropagationContextTest.kt | 86 ++++++++++++++++--- 2 files changed, 77 insertions(+), 12 deletions(-) diff --git a/sentry/src/main/java/io/sentry/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index 4fc1e61c271..82c03feac4b 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -1135,7 +1135,8 @@ public void reportFullyDisplayed() { final @Nullable String sentryTrace, final @Nullable List baggageHeaders) { @NotNull PropagationContext propagationContext = - PropagationContext.fromHeaders(getOptions().getLogger(), sentryTrace, baggageHeaders, getOptions()); + PropagationContext.fromHeaders( + getOptions().getLogger(), sentryTrace, baggageHeaders, getOptions()); configureScope( (scope) -> { scope.withPropagationContext( diff --git a/sentry/src/test/java/io/sentry/PropagationContextTest.kt b/sentry/src/test/java/io/sentry/PropagationContextTest.kt index 3431a88f37a..75068c55255 100644 --- a/sentry/src/test/java/io/sentry/PropagationContextTest.kt +++ b/sentry/src/test/java/io/sentry/PropagationContextTest.kt @@ -50,7 +50,11 @@ class PropagationContextTest { private val incomingTraceId = "bc6d53f15eb88f4320054569b8c553d4" private val sentryTrace = "bc6d53f15eb88f4320054569b8c553d4-b72fa28504b07285-1" - private fun makeOptions(dsnOrgId: String?, explicitOrgId: String? = null, strict: Boolean = false): SentryOptions { + private fun makeOptions( + dsnOrgId: String?, + explicitOrgId: String? = null, + strict: Boolean = false, + ): SentryOptions { val options = SentryOptions() if (dsnOrgId != null) { options.dsn = "https://key@o$dsnOrgId.ingest.sentry.io/123" @@ -73,70 +77,130 @@ class PropagationContextTest { @Test fun `strict=false, matching orgs - continues trace`() { val options = makeOptions(dsnOrgId = "1", strict = false) - val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage("1"), options) + val pc = + PropagationContext.fromHeaders( + NoOpLogger.getInstance(), + sentryTrace, + makeBaggage("1"), + options, + ) assertEquals(incomingTraceId, pc.traceId.toString()) } @Test fun `strict=false, baggage missing org - continues trace`() { val options = makeOptions(dsnOrgId = "1", strict = false) - val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage(null), options) + val pc = + PropagationContext.fromHeaders( + NoOpLogger.getInstance(), + sentryTrace, + makeBaggage(null), + options, + ) assertEquals(incomingTraceId, pc.traceId.toString()) } @Test fun `strict=false, sdk missing org - continues trace`() { val options = makeOptions(dsnOrgId = null, strict = false) - val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage("1"), options) + val pc = + PropagationContext.fromHeaders( + NoOpLogger.getInstance(), + sentryTrace, + makeBaggage("1"), + options, + ) assertEquals(incomingTraceId, pc.traceId.toString()) } @Test fun `strict=false, both missing org - continues trace`() { val options = makeOptions(dsnOrgId = null, strict = false) - val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage(null), options) + val pc = + PropagationContext.fromHeaders( + NoOpLogger.getInstance(), + sentryTrace, + makeBaggage(null), + options, + ) assertEquals(incomingTraceId, pc.traceId.toString()) } @Test fun `strict=false, mismatched orgs - starts new trace`() { val options = makeOptions(dsnOrgId = "2", strict = false) - val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage("1"), options) + val pc = + PropagationContext.fromHeaders( + NoOpLogger.getInstance(), + sentryTrace, + makeBaggage("1"), + options, + ) assertNotEquals(incomingTraceId, pc.traceId.toString()) } @Test fun `strict=true, matching orgs - continues trace`() { val options = makeOptions(dsnOrgId = "1", strict = true) - val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage("1"), options) + val pc = + PropagationContext.fromHeaders( + NoOpLogger.getInstance(), + sentryTrace, + makeBaggage("1"), + options, + ) assertEquals(incomingTraceId, pc.traceId.toString()) } @Test fun `strict=true, baggage missing org - starts new trace`() { val options = makeOptions(dsnOrgId = "1", strict = true) - val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage(null), options) + val pc = + PropagationContext.fromHeaders( + NoOpLogger.getInstance(), + sentryTrace, + makeBaggage(null), + options, + ) assertNotEquals(incomingTraceId, pc.traceId.toString()) } @Test fun `strict=true, sdk missing org - starts new trace`() { val options = makeOptions(dsnOrgId = null, strict = true) - val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage("1"), options) + val pc = + PropagationContext.fromHeaders( + NoOpLogger.getInstance(), + sentryTrace, + makeBaggage("1"), + options, + ) assertNotEquals(incomingTraceId, pc.traceId.toString()) } @Test fun `strict=true, both missing org - continues trace`() { val options = makeOptions(dsnOrgId = null, strict = true) - val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage(null), options) + val pc = + PropagationContext.fromHeaders( + NoOpLogger.getInstance(), + sentryTrace, + makeBaggage(null), + options, + ) assertEquals(incomingTraceId, pc.traceId.toString()) } @Test fun `strict=true, mismatched orgs - starts new trace`() { val options = makeOptions(dsnOrgId = "2", strict = true) - val pc = PropagationContext.fromHeaders(NoOpLogger.getInstance(), sentryTrace, makeBaggage("1"), options) + val pc = + PropagationContext.fromHeaders( + NoOpLogger.getInstance(), + sentryTrace, + makeBaggage("1"), + options, + ) assertNotEquals(incomingTraceId, pc.traceId.toString()) } } From c272ee4093b077ba12502299424084cbe07719e8 Mon Sep 17 00:00:00 2001 From: Giannis Gkiortzis <58184179+giortzisg@users.noreply.github.com> Date: Mon, 2 Mar 2026 17:01:47 +0100 Subject: [PATCH 3/8] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6d0c4104dd..f18223b6926 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - New APIs are `Sentry.setAttribute`, `Sentry.setAttributes`, `Sentry.removeAttribute` - Support collections and arrays in attribute type inference ([#5124](https://github.com/getsentry/sentry-java/pull/5124)) - Add support for `SENTRY_SAMPLE_RATE` environment variable / `sample-rate` property ([#5112](https://github.com/getsentry/sentry-java/pull/5112)) +- Add strict trace continuation support ([#5136](https://github.com/getsentry/sentry-java/pull/5136)) - Create `sentry-opentelemetry-otlp` and `sentry-opentelemetry-otlp-spring` modules for combining OpenTelemetry SDK OTLP export with Sentry SDK ([#5100](https://github.com/getsentry/sentry-java/pull/5100)) - OpenTelemetry is configured to send spans to Sentry directly using an OTLP endpoint. - Sentry only uses trace and span ID from OpenTelemetry (via `OpenTelemetryOtlpEventProcessor`) but will not send spans through OpenTelemetry nor use OpenTelemetry `Context` for `Scopes` propagation. From 1cfc2eaf4e3d10d3fc03a87022a2b1aba6e72c9a Mon Sep 17 00:00:00 2001 From: Giannis Gkiortzis <58184179+giortzisg@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:12:09 +0100 Subject: [PATCH 4/8] Update API surface file for strict trace continuation Add public API declarations for new org ID and strict trace continuation methods on Baggage, PropagationContext, and SentryOptions. Co-Authored-By: Claude Opus 4.6 --- sentry/api/sentry.api | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index a7bbb6c6cfa..55302361099 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -47,6 +47,7 @@ public final class io/sentry/Baggage { public static fun fromHeader (Ljava/util/List;ZLio/sentry/ILogger;)Lio/sentry/Baggage; public fun get (Ljava/lang/String;)Ljava/lang/String; public fun getEnvironment ()Ljava/lang/String; + public fun getOrgId ()Ljava/lang/String; public fun getPublicKey ()Ljava/lang/String; public fun getRelease ()Ljava/lang/String; public fun getReplayId ()Ljava/lang/String; @@ -62,6 +63,7 @@ public final class io/sentry/Baggage { public fun isShouldFreeze ()Z public fun set (Ljava/lang/String;Ljava/lang/String;)V public fun setEnvironment (Ljava/lang/String;)V + public fun setOrgId (Ljava/lang/String;)V public fun setPublicKey (Ljava/lang/String;)V public fun setRelease (Ljava/lang/String;)V public fun setReplayId (Ljava/lang/String;)V @@ -81,6 +83,7 @@ public final class io/sentry/Baggage { public final class io/sentry/Baggage$DSCKeys { public static final field ALL Ljava/util/List; public static final field ENVIRONMENT Ljava/lang/String; + public static final field ORG_ID Ljava/lang/String; public static final field PUBLIC_KEY Ljava/lang/String; public static final field RELEASE Ljava/lang/String; public static final field REPLAY_ID Ljava/lang/String; @@ -2267,6 +2270,7 @@ public final class io/sentry/PropagationContext { public static fun fromExistingTrace (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Double;)Lio/sentry/PropagationContext; public static fun fromHeaders (Lio/sentry/ILogger;Ljava/lang/String;Ljava/lang/String;)Lio/sentry/PropagationContext; public static fun fromHeaders (Lio/sentry/ILogger;Ljava/lang/String;Ljava/util/List;)Lio/sentry/PropagationContext; + public static fun fromHeaders (Lio/sentry/ILogger;Ljava/lang/String;Ljava/util/List;Lio/sentry/SentryOptions;)Lio/sentry/PropagationContext; public static fun fromHeaders (Lio/sentry/SentryTraceHeader;Lio/sentry/Baggage;Lio/sentry/SpanId;)Lio/sentry/PropagationContext; public fun getBaggage ()Lio/sentry/Baggage; public fun getParentSpanId ()Lio/sentry/SpanId; @@ -3569,6 +3573,7 @@ public class io/sentry/SentryOptions { public fun getDistribution ()Lio/sentry/SentryOptions$DistributionOptions; public fun getDistributionController ()Lio/sentry/IDistributionApi; public fun getDsn ()Ljava/lang/String; + public fun getEffectiveOrgId ()Ljava/lang/String; public fun getEnvelopeDiskCache ()Lio/sentry/cache/IEnvelopeCache; public fun getEnvelopeReader ()Lio/sentry/IEnvelopeReader; public fun getEnvironment ()Ljava/lang/String; @@ -3609,6 +3614,7 @@ public class io/sentry/SentryOptions { public fun getOnOversizedEvent ()Lio/sentry/SentryOptions$OnOversizedEventCallback; public fun getOpenTelemetryMode ()Lio/sentry/SentryOpenTelemetryMode; public fun getOptionsObservers ()Ljava/util/List; + public fun getOrgId ()Ljava/lang/String; public fun getOutboxPath ()Ljava/lang/String; public fun getPerformanceCollectors ()Ljava/util/List; public fun getProfileLifecycle ()Lio/sentry/ProfileLifecycle; @@ -3679,6 +3685,7 @@ public class io/sentry/SentryOptions { public fun isSendDefaultPii ()Z public fun isSendModules ()Z public fun isStartProfilerOnAppStart ()Z + public fun isStrictTraceContinuation ()Z public fun isTraceOptionsRequests ()Z public fun isTraceSampling ()Z public fun isTracingEnabled ()Z @@ -3762,6 +3769,7 @@ public class io/sentry/SentryOptions { public fun setOnDiscard (Lio/sentry/SentryOptions$OnDiscardCallback;)V public fun setOnOversizedEvent (Lio/sentry/SentryOptions$OnOversizedEventCallback;)V public fun setOpenTelemetryMode (Lio/sentry/SentryOpenTelemetryMode;)V + public fun setOrgId (Ljava/lang/String;)V public fun setPrintUncaughtStackTrace (Z)V public fun setProfileLifecycle (Lio/sentry/ProfileLifecycle;)V public fun setProfileSessionSampleRate (Ljava/lang/Double;)V @@ -3793,6 +3801,7 @@ public class io/sentry/SentryOptions { public fun setSpotlightConnectionUrl (Ljava/lang/String;)V public fun setSslSocketFactory (Ljavax/net/ssl/SSLSocketFactory;)V public fun setStartProfilerOnAppStart (Z)V + public fun setStrictTraceContinuation (Z)V public fun setTag (Ljava/lang/String;Ljava/lang/String;)V public fun setThreadChecker (Lio/sentry/util/thread/IThreadChecker;)V public fun setTraceOptionsRequests (Z)V From 108eb2d068c6afddccc5b20a2178267bfa664f31 Mon Sep 17 00:00:00 2001 From: Giannis Gkiortzis <58184179+giortzisg@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:11:51 +0100 Subject: [PATCH 5/8] Address review comments for strict trace continuation - Make Dsn.orgId final, remove unnecessary setter - Fix test signatures to use List for baggage headers - Add strictTraceContinuation and orgId to ExternalOptions and merge() - Add options to ManifestMetadataReader for Android manifest config - Use Sentry.getCurrentScopes().getOptions() in legacy fromHeaders overload - Improve CHANGELOG description with details about new options - Update API surface file for ExternalOptions changes Co-Authored-By: Claude Opus 4.6 --- CHANGELOG.md | 4 ++++ .../android/core/ManifestMetadataReader.java | 15 ++++++++++++ sentry/api/sentry.api | 4 ++++ sentry/src/main/java/io/sentry/Dsn.java | 10 ++++---- .../main/java/io/sentry/ExternalOptions.java | 23 +++++++++++++++++++ .../java/io/sentry/PropagationContext.java | 7 +++++- .../main/java/io/sentry/SentryOptions.java | 6 +++++ .../java/io/sentry/PropagationContextTest.kt | 20 ++++++++-------- 8 files changed, 72 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f18223b6926..716795b08cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ - Support collections and arrays in attribute type inference ([#5124](https://github.com/getsentry/sentry-java/pull/5124)) - Add support for `SENTRY_SAMPLE_RATE` environment variable / `sample-rate` property ([#5112](https://github.com/getsentry/sentry-java/pull/5112)) - Add strict trace continuation support ([#5136](https://github.com/getsentry/sentry-java/pull/5136)) + - The SDK now extracts `org_id` from the DSN host and propagates it via `sentry-org_id` in the baggage header. + - When an incoming trace has a mismatched `org_id`, the SDK starts a new trace instead of continuing the foreign one. + - New option `strictTraceContinuation` (default `false`): when enabled, both the SDK's org ID and the incoming baggage org ID must be present and match for a trace to be continued. + - New option `orgId`: allows explicitly setting the organization ID for self-hosted and Relay setups where it cannot be extracted from the DSN. - Create `sentry-opentelemetry-otlp` and `sentry-opentelemetry-otlp-spring` modules for combining OpenTelemetry SDK OTLP export with Sentry SDK ([#5100](https://github.com/getsentry/sentry-java/pull/5100)) - OpenTelemetry is configured to send spans to Sentry directly using an OTLP endpoint. - Sentry only uses trace and span ID from OpenTelemetry (via `OpenTelemetryOtlpEventProcessor`) but will not send spans through OpenTelemetry nor use OpenTelemetry `Context` for `Scopes` propagation. diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java index 0fd217794e2..2f51f873c37 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java @@ -167,6 +167,9 @@ final class ManifestMetadataReader { static final String FEEDBACK_SHOW_BRANDING = "io.sentry.feedback.show-branding"; + static final String STRICT_TRACE_CONTINUATION = "io.sentry.strict-trace-continuation"; + static final String ORG_ID = "io.sentry.org-id"; + static final String SPOTLIGHT_ENABLE = "io.sentry.spotlight.enable"; static final String SPOTLIGHT_CONNECTION_URL = "io.sentry.spotlight.url"; @@ -658,6 +661,18 @@ static void applyMetadata( feedbackOptions.setShowBranding( readBool(metadata, logger, FEEDBACK_SHOW_BRANDING, feedbackOptions.isShowBranding())); + options.setStrictTraceContinuation( + readBool( + metadata, + logger, + STRICT_TRACE_CONTINUATION, + options.isStrictTraceContinuation())); + + final @Nullable String orgId = readString(metadata, logger, ORG_ID, null); + if (orgId != null) { + options.setOrgId(orgId); + } + options.setEnableSpotlight( readBool(metadata, logger, SPOTLIGHT_ENABLE, options.isEnableSpotlight())); diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 55302361099..3fa3d8ff870 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -504,6 +504,7 @@ public final class io/sentry/ExternalOptions { public fun getInAppExcludes ()Ljava/util/List; public fun getInAppIncludes ()Ljava/util/List; public fun getMaxRequestBodySize ()Lio/sentry/SentryOptions$RequestSize; + public fun getOrgId ()Ljava/lang/String; public fun getPrintUncaughtStackTrace ()Ljava/lang/Boolean; public fun getProfileLifecycle ()Lio/sentry/ProfileLifecycle; public fun getProfileSessionSampleRate ()Ljava/lang/Double; @@ -531,6 +532,7 @@ public final class io/sentry/ExternalOptions { public fun isGlobalHubMode ()Ljava/lang/Boolean; public fun isSendDefaultPii ()Ljava/lang/Boolean; public fun isSendModules ()Ljava/lang/Boolean; + public fun isStrictTraceContinuation ()Ljava/lang/Boolean; public fun setCaptureOpenTelemetryEvents (Ljava/lang/Boolean;)V public fun setCron (Lio/sentry/SentryOptions$Cron;)V public fun setDebug (Ljava/lang/Boolean;)V @@ -553,6 +555,7 @@ public final class io/sentry/ExternalOptions { public fun setIgnoredErrors (Ljava/util/List;)V public fun setIgnoredTransactions (Ljava/util/List;)V public fun setMaxRequestBodySize (Lio/sentry/SentryOptions$RequestSize;)V + public fun setOrgId (Ljava/lang/String;)V public fun setPrintUncaughtStackTrace (Ljava/lang/Boolean;)V public fun setProfileLifecycle (Lio/sentry/ProfileLifecycle;)V public fun setProfileSessionSampleRate (Ljava/lang/Double;)V @@ -567,6 +570,7 @@ public final class io/sentry/ExternalOptions { public fun setSendModules (Ljava/lang/Boolean;)V public fun setServerName (Ljava/lang/String;)V public fun setSpotlightConnectionUrl (Ljava/lang/String;)V + public fun setStrictTraceContinuation (Ljava/lang/Boolean;)V public fun setTag (Ljava/lang/String;Ljava/lang/String;)V public fun setTracesSampleRate (Ljava/lang/Double;)V } diff --git a/sentry/src/main/java/io/sentry/Dsn.java b/sentry/src/main/java/io/sentry/Dsn.java index e28e831848e..0d21499b5fc 100644 --- a/sentry/src/main/java/io/sentry/Dsn.java +++ b/sentry/src/main/java/io/sentry/Dsn.java @@ -15,7 +15,7 @@ final class Dsn { private final @Nullable String secretKey; private final @NotNull String publicKey; private final @NotNull URI sentryUri; - private @Nullable String orgId; + private final @Nullable String orgId; /* / The project ID which the authenticated user is bound to. @@ -94,13 +94,15 @@ URI getSentryUri() { scheme, null, uri.getHost(), uri.getPort(), path + "api/" + projectId, null, null); // Extract org ID from host (e.g., "o123.ingest.sentry.io" -> "123") + String extractedOrgId = null; final String host = uri.getHost(); if (host != null) { final Matcher matcher = ORG_ID_PATTERN.matcher(host); if (matcher.find()) { - orgId = matcher.group(1); + extractedOrgId = matcher.group(1); } } + orgId = extractedOrgId; } catch (Throwable e) { throw new IllegalArgumentException(e); } @@ -109,8 +111,4 @@ URI getSentryUri() { public @Nullable String getOrgId() { return orgId; } - - void setOrgId(final @Nullable String orgId) { - this.orgId = orgId; - } } diff --git a/sentry/src/main/java/io/sentry/ExternalOptions.java b/sentry/src/main/java/io/sentry/ExternalOptions.java index 9eaf26b202f..5473876aeaf 100644 --- a/sentry/src/main/java/io/sentry/ExternalOptions.java +++ b/sentry/src/main/java/io/sentry/ExternalOptions.java @@ -63,6 +63,9 @@ public final class ExternalOptions { private @Nullable String profilingTracesDirPath; private @Nullable ProfileLifecycle profileLifecycle; + private @Nullable Boolean strictTraceContinuation; + private @Nullable String orgId; + private @Nullable SentryOptions.Cron cron; @SuppressWarnings("unchecked") @@ -213,6 +216,10 @@ public final class ExternalOptions { options.setCron(cron); } + options.setStrictTraceContinuation( + propertiesProvider.getBooleanProperty("strict-trace-continuation")); + options.setOrgId(propertiesProvider.getProperty("org-id")); + options.setEnableSpotlight(propertiesProvider.getBooleanProperty("enable-spotlight")); options.setSpotlightConnectionUrl(propertiesProvider.getProperty("spotlight-connection-url")); options.setProfileSessionSampleRate( @@ -589,6 +596,22 @@ public void setProfilingTracesDirPath(@Nullable String profilingTracesDirPath) { this.profilingTracesDirPath = profilingTracesDirPath; } + public @Nullable Boolean isStrictTraceContinuation() { + return strictTraceContinuation; + } + + public void setStrictTraceContinuation(final @Nullable Boolean strictTraceContinuation) { + this.strictTraceContinuation = strictTraceContinuation; + } + + public @Nullable String getOrgId() { + return orgId; + } + + public void setOrgId(final @Nullable String orgId) { + this.orgId = orgId; + } + public @Nullable ProfileLifecycle getProfileLifecycle() { return profileLifecycle; } diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index 772013ec567..5accabd5b12 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -23,7 +23,12 @@ public static PropagationContext fromHeaders( final @NotNull ILogger logger, final @Nullable String sentryTraceHeaderString, final @Nullable List baggageHeaderStrings) { - return fromHeaders(logger, sentryTraceHeaderString, baggageHeaderStrings, null); + @Nullable SentryOptions options = null; + try { + options = Sentry.getCurrentScopes().getOptions(); + } catch (Throwable ignored) { + } + return fromHeaders(logger, sentryTraceHeaderString, baggageHeaderStrings, options); } public static @NotNull PropagationContext fromHeaders( diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 9457a369713..6ce07f3ff67 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -3571,6 +3571,12 @@ public void merge(final @NotNull ExternalOptions options) { if (options.getProfileLifecycle() != null) { setProfileLifecycle(options.getProfileLifecycle()); } + if (options.isStrictTraceContinuation() != null) { + setStrictTraceContinuation(options.isStrictTraceContinuation()); + } + if (options.getOrgId() != null) { + setOrgId(options.getOrgId()); + } } private @NotNull SdkVersion createSdkVersion() { diff --git a/sentry/src/test/java/io/sentry/PropagationContextTest.kt b/sentry/src/test/java/io/sentry/PropagationContextTest.kt index 75068c55255..b27c28b9da6 100644 --- a/sentry/src/test/java/io/sentry/PropagationContextTest.kt +++ b/sentry/src/test/java/io/sentry/PropagationContextTest.kt @@ -81,7 +81,7 @@ class PropagationContextTest { PropagationContext.fromHeaders( NoOpLogger.getInstance(), sentryTrace, - makeBaggage("1"), + listOf(makeBaggage("1")), options, ) assertEquals(incomingTraceId, pc.traceId.toString()) @@ -94,7 +94,7 @@ class PropagationContextTest { PropagationContext.fromHeaders( NoOpLogger.getInstance(), sentryTrace, - makeBaggage(null), + listOf(makeBaggage(null)), options, ) assertEquals(incomingTraceId, pc.traceId.toString()) @@ -107,7 +107,7 @@ class PropagationContextTest { PropagationContext.fromHeaders( NoOpLogger.getInstance(), sentryTrace, - makeBaggage("1"), + listOf(makeBaggage("1")), options, ) assertEquals(incomingTraceId, pc.traceId.toString()) @@ -120,7 +120,7 @@ class PropagationContextTest { PropagationContext.fromHeaders( NoOpLogger.getInstance(), sentryTrace, - makeBaggage(null), + listOf(makeBaggage(null)), options, ) assertEquals(incomingTraceId, pc.traceId.toString()) @@ -133,7 +133,7 @@ class PropagationContextTest { PropagationContext.fromHeaders( NoOpLogger.getInstance(), sentryTrace, - makeBaggage("1"), + listOf(makeBaggage("1")), options, ) assertNotEquals(incomingTraceId, pc.traceId.toString()) @@ -146,7 +146,7 @@ class PropagationContextTest { PropagationContext.fromHeaders( NoOpLogger.getInstance(), sentryTrace, - makeBaggage("1"), + listOf(makeBaggage("1")), options, ) assertEquals(incomingTraceId, pc.traceId.toString()) @@ -159,7 +159,7 @@ class PropagationContextTest { PropagationContext.fromHeaders( NoOpLogger.getInstance(), sentryTrace, - makeBaggage(null), + listOf(makeBaggage(null)), options, ) assertNotEquals(incomingTraceId, pc.traceId.toString()) @@ -172,7 +172,7 @@ class PropagationContextTest { PropagationContext.fromHeaders( NoOpLogger.getInstance(), sentryTrace, - makeBaggage("1"), + listOf(makeBaggage("1")), options, ) assertNotEquals(incomingTraceId, pc.traceId.toString()) @@ -185,7 +185,7 @@ class PropagationContextTest { PropagationContext.fromHeaders( NoOpLogger.getInstance(), sentryTrace, - makeBaggage(null), + listOf(makeBaggage(null)), options, ) assertEquals(incomingTraceId, pc.traceId.toString()) @@ -198,7 +198,7 @@ class PropagationContextTest { PropagationContext.fromHeaders( NoOpLogger.getInstance(), sentryTrace, - makeBaggage("1"), + listOf(makeBaggage("1")), options, ) assertNotEquals(incomingTraceId, pc.traceId.toString()) From e30064c93043b0a8bc9added2934675bbd849450 Mon Sep 17 00:00:00 2001 From: Giannis Gkiortzis <58184179+giortzisg@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:13:45 +0100 Subject: [PATCH 6/8] Fix compilation errors after rebase on main - Add comment to empty catch block in PropagationContext to satisfy -Werror - Add setOrgId setter to Dsn class (remove final modifier on orgId field) to support the existing test for org ID override Co-Authored-By: Claude Opus 4.6 --- sentry/src/main/java/io/sentry/Dsn.java | 6 +++++- sentry/src/main/java/io/sentry/PropagationContext.java | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/Dsn.java b/sentry/src/main/java/io/sentry/Dsn.java index 0d21499b5fc..8b039849c01 100644 --- a/sentry/src/main/java/io/sentry/Dsn.java +++ b/sentry/src/main/java/io/sentry/Dsn.java @@ -15,7 +15,7 @@ final class Dsn { private final @Nullable String secretKey; private final @NotNull String publicKey; private final @NotNull URI sentryUri; - private final @Nullable String orgId; + private @Nullable String orgId; /* / The project ID which the authenticated user is bound to. @@ -111,4 +111,8 @@ URI getSentryUri() { public @Nullable String getOrgId() { return orgId; } + + public void setOrgId(final @Nullable String orgId) { + this.orgId = orgId; + } } diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index 5accabd5b12..0e82c5f1c27 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -27,6 +27,7 @@ public static PropagationContext fromHeaders( try { options = Sentry.getCurrentScopes().getOptions(); } catch (Throwable ignored) { + // options may not be available if Sentry is not initialized } return fromHeaders(logger, sentryTraceHeaderString, baggageHeaderStrings, options); } From 45457351697a30abfe9489b414fbb07347e8a7f4 Mon Sep 17 00:00:00 2001 From: Giannis Gkiortzis <58184179+giortzisg@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:20:11 +0100 Subject: [PATCH 7/8] fix: Move changelog entry to Unreleased section --- CHANGELOG.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 716795b08cb..c9db1465cb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## Unreleased + +### Features + +- Add strict trace continuation support ([#5136](https://github.com/getsentry/sentry-java/pull/5136)) + - The SDK now extracts `org_id` from the DSN host and propagates it via `sentry-org_id` in the baggage header. + - When an incoming trace has a mismatched `org_id`, the SDK starts a new trace instead of continuing the foreign one. + - New option `strictTraceContinuation` (default `false`): when enabled, both the SDK's org ID and the incoming baggage org ID must be present and match for a trace to be continued. + - New option `orgId`: allows explicitly setting the organization ID for self-hosted and Relay setups where it cannot be extracted from the DSN. + ## 8.34.0 ### Features @@ -9,11 +19,6 @@ - New APIs are `Sentry.setAttribute`, `Sentry.setAttributes`, `Sentry.removeAttribute` - Support collections and arrays in attribute type inference ([#5124](https://github.com/getsentry/sentry-java/pull/5124)) - Add support for `SENTRY_SAMPLE_RATE` environment variable / `sample-rate` property ([#5112](https://github.com/getsentry/sentry-java/pull/5112)) -- Add strict trace continuation support ([#5136](https://github.com/getsentry/sentry-java/pull/5136)) - - The SDK now extracts `org_id` from the DSN host and propagates it via `sentry-org_id` in the baggage header. - - When an incoming trace has a mismatched `org_id`, the SDK starts a new trace instead of continuing the foreign one. - - New option `strictTraceContinuation` (default `false`): when enabled, both the SDK's org ID and the incoming baggage org ID must be present and match for a trace to be continued. - - New option `orgId`: allows explicitly setting the organization ID for self-hosted and Relay setups where it cannot be extracted from the DSN. - Create `sentry-opentelemetry-otlp` and `sentry-opentelemetry-otlp-spring` modules for combining OpenTelemetry SDK OTLP export with Sentry SDK ([#5100](https://github.com/getsentry/sentry-java/pull/5100)) - OpenTelemetry is configured to send spans to Sentry directly using an OTLP endpoint. - Sentry only uses trace and span ID from OpenTelemetry (via `OpenTelemetryOtlpEventProcessor`) but will not send spans through OpenTelemetry nor use OpenTelemetry `Context` for `Scopes` propagation. From 61ada6a18d6576c81723616f3b05c13a64ea5e4a Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Wed, 4 Mar 2026 13:20:36 +0000 Subject: [PATCH 8/8] Format code --- .../java/io/sentry/android/core/ManifestMetadataReader.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java index 2f51f873c37..163571f4c99 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java @@ -663,10 +663,7 @@ static void applyMetadata( options.setStrictTraceContinuation( readBool( - metadata, - logger, - STRICT_TRACE_CONTINUATION, - options.isStrictTraceContinuation())); + metadata, logger, STRICT_TRACE_CONTINUATION, options.isStrictTraceContinuation())); final @Nullable String orgId = readString(metadata, logger, ORG_ID, null); if (orgId != null) {