From 104267513dbb4536157fb15763bbd1f437080fa1 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 6 May 2026 13:29:08 +0200 Subject: [PATCH 1/9] ci: Fix Spring Boot matrix version updates Match the TOML version catalog format when overriding Spring Boot versions in matrix jobs. Preserve whitespace around the assignment and replace the quoted version value so the CI jobs actually test the requested matrix version. Co-Authored-By: Claude --- .github/workflows/spring-boot-2-matrix.yml | 2 +- .github/workflows/spring-boot-3-matrix.yml | 2 +- .github/workflows/spring-boot-4-matrix.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/spring-boot-2-matrix.yml b/.github/workflows/spring-boot-2-matrix.yml index 38aaacec27..ca14fdc419 100644 --- a/.github/workflows/spring-boot-2-matrix.yml +++ b/.github/workflows/spring-boot-2-matrix.yml @@ -62,7 +62,7 @@ jobs: - name: Update Spring Boot 2.x version run: | - sed -i 's/^springboot2=.*/springboot2=${{ matrix.springboot-version }}/' gradle/libs.versions.toml + sed -i 's/^\(springboot2[[:space:]]*=[[:space:]]*\)".*"/\1"${{ matrix.springboot-version }}"/' gradle/libs.versions.toml echo "Updated Spring Boot 2.x version to ${{ matrix.springboot-version }}" - name: Exclude android modules from build diff --git a/.github/workflows/spring-boot-3-matrix.yml b/.github/workflows/spring-boot-3-matrix.yml index 629535e282..00635e7254 100644 --- a/.github/workflows/spring-boot-3-matrix.yml +++ b/.github/workflows/spring-boot-3-matrix.yml @@ -62,7 +62,7 @@ jobs: - name: Update Spring Boot 3.x version run: | - sed -i 's/^springboot3=.*/springboot3=${{ matrix.springboot-version }}/' gradle/libs.versions.toml + sed -i 's/^\(springboot3[[:space:]]*=[[:space:]]*\)".*"/\1"${{ matrix.springboot-version }}"/' gradle/libs.versions.toml echo "Updated Spring Boot 3.x version to ${{ matrix.springboot-version }}" - name: Exclude android modules from build diff --git a/.github/workflows/spring-boot-4-matrix.yml b/.github/workflows/spring-boot-4-matrix.yml index bbd4f986d9..aa4671cdab 100644 --- a/.github/workflows/spring-boot-4-matrix.yml +++ b/.github/workflows/spring-boot-4-matrix.yml @@ -62,7 +62,7 @@ jobs: - name: Update Spring Boot 4.x version run: | - sed -i 's/^springboot4=.*/springboot4=${{ matrix.springboot-version }}/' gradle/libs.versions.toml + sed -i 's/^\(springboot4[[:space:]]*=[[:space:]]*\)".*"/\1"${{ matrix.springboot-version }}"/' gradle/libs.versions.toml echo "Updated Spring Boot 4.x version to ${{ matrix.springboot-version }}" - name: Exclude android modules from build From 917ebada4d1211f44eaabf5ac39b01f3c05004db Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 6 May 2026 15:02:01 +0200 Subject: [PATCH 2/9] ci: Limit Spring Boot matrix to supported versions The matrix jobs now actually update the version catalog. Remove Spring Boot versions that the current sample setup cannot build with the repository's Spring GraphQL integrations and Gradle version. Co-Authored-By: Claude --- .github/workflows/spring-boot-2-matrix.yml | 2 +- .github/workflows/spring-boot-3-matrix.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/spring-boot-2-matrix.yml b/.github/workflows/spring-boot-2-matrix.yml index ca14fdc419..e9fa9939f1 100644 --- a/.github/workflows/spring-boot-2-matrix.yml +++ b/.github/workflows/spring-boot-2-matrix.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - springboot-version: [ '2.1.0', '2.2.5', '2.4.13', '2.5.15', '2.6.15', '2.7.0', '2.7.18' ] + springboot-version: [ '2.7.17', '2.7.18' ] name: Spring Boot ${{ matrix.springboot-version }} env: diff --git a/.github/workflows/spring-boot-3-matrix.yml b/.github/workflows/spring-boot-3-matrix.yml index 00635e7254..4572d43409 100644 --- a/.github/workflows/spring-boot-3-matrix.yml +++ b/.github/workflows/spring-boot-3-matrix.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - springboot-version: [ '3.0.0', '3.2.12', '3.3.13', '3.4.13', '3.5.13' ] + springboot-version: [ '3.5.0', '3.5.13' ] name: Spring Boot ${{ matrix.springboot-version }} env: From 52df6e99f30e2d267e1de530c4f0cdab60ebfa80 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 11 May 2026 14:03:38 +0200 Subject: [PATCH 3/9] ci(spring): Restore Spring Boot matrix coverage Expand the Spring Boot 2 and 3 CI matrices to cover supported minor versions. Exclude GraphQL from Spring Boot 2 versions before 2.7 because the starter is unavailable there. Keep the Spring Boot 3 Gradle plugin pinned to a Gradle-compatible version while importing the tested Spring Boot BOM in samples, so the matrix exercises the intended runtime dependencies. Co-Authored-By: Claude --- .github/workflows/spring-boot-2-matrix.yml | 13 +++-- .github/workflows/spring-boot-3-matrix.yml | 2 +- gradle/libs.versions.toml | 4 +- .../build.gradle.kts | 9 +++- .../build.gradle.kts | 6 +++ .../build.gradle.kts | 6 +++ .../build.gradle.kts | 47 +++++++++++++++---- .../build.gradle.kts | 47 +++++++++++++++---- .../build.gradle.kts | 6 +++ .../build.gradle.kts | 41 +++++++++++++--- .../boot/DistributedTracingController.java | 15 +++--- .../samples/spring/boot/PersonService.java | 2 +- .../build.gradle.kts | 45 ++++++++++++++---- .../build.gradle.kts | 6 +-- sentry-spring-boot/build.gradle.kts | 2 +- .../spring/boot/SentryAutoConfiguration.java | 14 +++--- .../boot/SentrySpringVersionChecker.java | 3 +- sentry-spring/build.gradle.kts | 2 +- .../spring/webflux/SentryWebFilter.java | 3 +- 19 files changed, 206 insertions(+), 67 deletions(-) diff --git a/.github/workflows/spring-boot-2-matrix.yml b/.github/workflows/spring-boot-2-matrix.yml index e9fa9939f1..250ce41652 100644 --- a/.github/workflows/spring-boot-2-matrix.yml +++ b/.github/workflows/spring-boot-2-matrix.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - springboot-version: [ '2.7.17', '2.7.18' ] + springboot-version: [ '2.1.0', '2.2.5', '2.4.13', '2.5.15', '2.6.15', '2.7.0', '2.7.18' ] name: Spring Boot ${{ matrix.springboot-version }} env: @@ -62,8 +62,15 @@ jobs: - name: Update Spring Boot 2.x version run: | - sed -i 's/^\(springboot2[[:space:]]*=[[:space:]]*\)".*"/\1"${{ matrix.springboot-version }}"/' gradle/libs.versions.toml - echo "Updated Spring Boot 2.x version to ${{ matrix.springboot-version }}" + springboot_version="${{ matrix.springboot-version }}" + if [[ "$springboot_version" =~ ^2\.[0-3]\. ]]; then + springboot_version="${springboot_version}.RELEASE" + fi + if [[ ! "$springboot_version" =~ ^2\.7\. ]]; then + echo "ORG_GRADLE_PROJECT_excludeGraphql=true" >> "$GITHUB_ENV" + fi + sed -i 's/^\(springboot2[[:space:]]*=[[:space:]]*\)".*"/\1"'"$springboot_version"'"/' gradle/libs.versions.toml + echo "Updated Spring Boot 2.x version to $springboot_version" - name: Exclude android modules from build run: | diff --git a/.github/workflows/spring-boot-3-matrix.yml b/.github/workflows/spring-boot-3-matrix.yml index 4572d43409..930b1f4338 100644 --- a/.github/workflows/spring-boot-3-matrix.yml +++ b/.github/workflows/spring-boot-3-matrix.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - springboot-version: [ '3.5.0', '3.5.13' ] + springboot-version: [ '3.2.12', '3.3.13', '3.4.13', '3.5.13' ] name: Spring Boot ${{ matrix.springboot-version }} env: diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ab39c981b4..10f5e665b8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,6 +32,7 @@ retrofit = "2.9.0" slf4j = "1.7.30" springboot2 = "2.7.18" springboot3 = "3.5.0" +springboot3Plugin = "3.5.0" springboot4 = "4.0.0" # Android targetSdk = "36" @@ -61,7 +62,7 @@ detekt = { id = "io.gitlab.arturbosch.detekt", version = "1.23.8" } jacoco-android = { id = "com.mxalbert.gradle.jacoco-android", version = "0.2.0" } kover = { id = "org.jetbrains.kotlinx.kover", version = "0.7.3" } vanniktech-maven-publish = { id = "com.vanniktech.maven.publish", version = "0.30.0" } -springboot3 = { id = "org.springframework.boot", version.ref = "springboot3" } +springboot3 = { id = "org.springframework.boot", version.ref = "springboot3Plugin" } springboot4 = { id = "org.springframework.boot", version.ref = "springboot4" } spring-dependency-management = { id = "io.spring.dependency-management", version = "1.1.7" } gretty = { id = "org.gretty", version = "4.0.0" } @@ -160,6 +161,7 @@ slf4j2-api = { module = "org.slf4j:slf4j-api", version = "2.0.5" } spotlessLib = { module = "com.diffplug.spotless:com.diffplug.spotless.gradle.plugin", version.ref = "spotless"} springboot2-bom = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "springboot2" } springboot-starter = { module = "org.springframework.boot:spring-boot-starter", version.ref = "springboot2" } +spring-graphql = { module = "org.springframework.graphql:spring-graphql", version = "1.0.6" } springboot-starter-graphql = { module = "org.springframework.boot:spring-boot-starter-graphql", version.ref = "springboot2" } springboot-starter-quartz = { module = "org.springframework.boot:spring-boot-starter-quartz", version.ref = "springboot2" } springboot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test", version.ref = "springboot2" } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts index ed0af32b03..7137a71d8b 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts @@ -18,6 +18,13 @@ java.targetCompatibility = JavaVersion.VERSION_17 repositories { mavenCentral() } +dependencyManagement { + imports { + mavenBom("org.springframework.boot:spring-boot-dependencies:${libs.versions.springboot3.get()}") + mavenBom(libs.otel.instrumentation.bom.get().toString()) + } +} + // Apollo 4.x requires coroutines 1.9.0+, override Spring Boot's managed version extra["kotlin-coroutines.version"] = "1.9.0" @@ -79,8 +86,6 @@ dependencies { testImplementation("ch.qos.logback:logback-core:1.5.16") } -dependencyManagement { imports { mavenBom(libs.otel.instrumentation.bom.get().toString()) } } - configure { test { java.srcDir("src/test/java") } } tasks.register("systemTest").configure { diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts index d3d66c469b..1bf1fca205 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts @@ -19,6 +19,12 @@ java.targetCompatibility = JavaVersion.VERSION_17 repositories { mavenCentral() } +dependencyManagement { + imports { + mavenBom("org.springframework.boot:spring-boot-dependencies:${libs.versions.springboot3.get()}") + } +} + // Apollo 4.x requires coroutines 1.9.0+, override Spring Boot's managed version extra["kotlin-coroutines.version"] = "1.9.0" diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts index ae3ef70ad7..2bb95b9b41 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts @@ -18,6 +18,12 @@ java.targetCompatibility = JavaVersion.VERSION_17 repositories { mavenCentral() } +dependencyManagement { + imports { + mavenBom("org.springframework.boot:spring-boot-dependencies:${libs.versions.springboot3.get()}") + } +} + // Apollo 4.x requires coroutines 1.9.0+, override Spring Boot's managed version extra["kotlin-coroutines.version"] = "1.9.0" diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index f1665f513d..4bd5fe6b37 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -15,25 +15,35 @@ group = "io.sentry.sample.spring-boot" version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = JavaVersion.VERSION_17 +java.sourceCompatibility = JavaVersion.VERSION_11 -java.targetCompatibility = JavaVersion.VERSION_17 +java.targetCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() } +fun springBoot2SupportsGraphql(): Boolean { + val version = libs.versions.springboot2.get().removeSuffix(".RELEASE") + val parts = version.split(".").map { it.toIntOrNull() ?: 0 } + val major = parts.getOrElse(0) { 0 } + val minor = parts.getOrElse(1) { 0 } + return major > 2 || (major == 2 && minor >= 7) +} + +val includeGraphql = !project.hasProperty("excludeGraphql") && springBoot2SupportsGraphql() + configure { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } tasks.withType().configureEach { - compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 } tasks.withType().configureEach { kotlin { compilerOptions.freeCompilerArgs = listOf("-Xjsr305=strict") - compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 } } @@ -43,7 +53,9 @@ dependencies { implementation(libs.springboot.starter) implementation(libs.springboot.starter.actuator) implementation(libs.springboot.starter.aop) - implementation(libs.springboot.starter.graphql) + if (includeGraphql) { + implementation(libs.springboot.starter.graphql) + } implementation(libs.springboot.starter.jdbc) implementation(libs.springboot.starter.quartz) implementation(libs.springboot.starter.security) @@ -55,7 +67,9 @@ dependencies { implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) implementation(projects.sentrySpringBootStarter) implementation(projects.sentryLogback) - implementation(projects.sentryGraphql) + if (includeGraphql) { + implementation(projects.sentryGraphql) + } implementation(projects.sentryQuartz) implementation(projects.sentryOpentelemetry.sentryOpentelemetryAgentlessSpring) implementation(projects.sentryAsyncProfiler) @@ -103,7 +117,15 @@ tasks.jar { tasks.startScripts { dependsOn(tasks.shadowJar) } -configure { test { java.srcDir("src/test/java") } } +configure { + main { + if (!includeGraphql) { + java.exclude("**/graphql/**") + resources.exclude("graphql/**") + } + } + test { java.srcDir("src/test/java") } +} tasks.register("systemTest").configure { group = "verification" @@ -121,7 +143,12 @@ tasks.register("systemTest").configure { minHeapSize = "128m" maxHeapSize = "1g" - filter { includeTestsMatching("io.sentry.systemtest*") } + filter { + includeTestsMatching("io.sentry.systemtest*") + if (!includeGraphql) { + excludeTestsMatching("io.sentry.systemtest.Graphql*") + } + } } tasks.named("test").configure { diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index 7c84875ca0..563ae56b07 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -15,22 +15,32 @@ group = "io.sentry.sample.spring-boot" version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = JavaVersion.VERSION_17 +java.sourceCompatibility = JavaVersion.VERSION_11 -java.targetCompatibility = JavaVersion.VERSION_17 +java.targetCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() } +fun springBoot2SupportsGraphql(): Boolean { + val version = libs.versions.springboot2.get().removeSuffix(".RELEASE") + val parts = version.split(".").map { it.toIntOrNull() ?: 0 } + val major = parts.getOrElse(0) { 0 } + val minor = parts.getOrElse(1) { 0 } + return major > 2 || (major == 2 && minor >= 7) +} + +val includeGraphql = !project.hasProperty("excludeGraphql") && springBoot2SupportsGraphql() + configure { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } tasks.withType().configureEach { - compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 kotlin { compilerOptions.freeCompilerArgs = listOf("-Xjsr305=strict") - compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 } } @@ -39,7 +49,9 @@ dependencies { implementation(libs.springboot.starter) implementation(libs.springboot.starter.actuator) implementation(libs.springboot.starter.aop) - implementation(libs.springboot.starter.graphql) + if (includeGraphql) { + implementation(libs.springboot.starter.graphql) + } implementation(libs.springboot.starter.jdbc) implementation(libs.springboot.starter.quartz) implementation(libs.springboot.starter.security) @@ -51,7 +63,9 @@ dependencies { implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) implementation(projects.sentrySpringBootStarter) implementation(projects.sentryLogback) - implementation(projects.sentryGraphql) + if (includeGraphql) { + implementation(projects.sentryGraphql) + } implementation(projects.sentryQuartz) implementation(projects.sentryAsyncProfiler) implementation(libs.otel) @@ -99,7 +113,15 @@ tasks.jar { tasks.startScripts { dependsOn(tasks.shadowJar) } -configure { test { java.srcDir("src/test/java") } } +configure { + main { + if (!includeGraphql) { + java.exclude("**/graphql/**") + resources.exclude("graphql/**") + } + } + test { java.srcDir("src/test/java") } +} tasks.register("bootRunWithAgent").configure { group = "application" @@ -141,7 +163,12 @@ tasks.register("systemTest").configure { minHeapSize = "128m" maxHeapSize = "1g" - filter { includeTestsMatching("io.sentry.systemtest*") } + filter { + includeTestsMatching("io.sentry.systemtest*") + if (!includeGraphql) { + excludeTestsMatching("io.sentry.systemtest.Graphql*") + } + } } tasks.named("test").configure { diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts index d5b0454357..76e29fb87c 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts @@ -18,6 +18,12 @@ java.targetCompatibility = JavaVersion.VERSION_17 repositories { mavenCentral() } +dependencyManagement { + imports { + mavenBom("org.springframework.boot:spring-boot-dependencies:${libs.versions.springboot3.get()}") + } +} + // Apollo 4.x requires coroutines 1.9.0+, override Spring Boot's managed version extra["kotlin-coroutines.version"] = "1.9.0" diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index b10b30737d..08a98ae166 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -15,22 +15,36 @@ group = "io.sentry.sample.spring-boot" version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = JavaVersion.VERSION_17 +java.sourceCompatibility = JavaVersion.VERSION_11 -java.targetCompatibility = JavaVersion.VERSION_17 +java.targetCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() } +fun springBoot2SupportsGraphql(): Boolean { + val version = libs.versions.springboot2.get().removeSuffix(".RELEASE") + val parts = version.split(".").map { it.toIntOrNull() ?: 0 } + val major = parts.getOrElse(0) { 0 } + val minor = parts.getOrElse(1) { 0 } + return major > 2 || (major == 2 && minor >= 7) +} + +val includeGraphql = !project.hasProperty("excludeGraphql") && springBoot2SupportsGraphql() + dependencies { implementation(platform(libs.springboot2.bom)) implementation(libs.springboot.starter.actuator) - implementation(libs.springboot.starter.graphql) + if (includeGraphql) { + implementation(libs.springboot.starter.graphql) + } implementation(libs.springboot.starter.webflux) implementation(Config.Libs.kotlinReflect) implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) implementation(projects.sentrySpringBootStarter) implementation(projects.sentryLogback) - implementation(projects.sentryGraphql) + if (includeGraphql) { + implementation(projects.sentryGraphql) + } implementation(projects.sentryAsyncProfiler) testImplementation(kotlin(Config.kotlinStdLib)) @@ -68,12 +82,20 @@ tasks.jar { tasks.startScripts { dependsOn(tasks.shadowJar) } -configure { test { java.srcDir("src/test/java") } } +configure { + main { + if (!includeGraphql) { + java.exclude("**/graphql/**") + resources.exclude("graphql/**") + } + } + test { java.srcDir("src/test/java") } +} tasks.withType().configureEach { kotlin { compilerOptions.freeCompilerArgs = listOf("-Xjsr305=strict") - compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 } } @@ -93,7 +115,12 @@ tasks.register("systemTest").configure { minHeapSize = "128m" maxHeapSize = "1g" - filter { includeTestsMatching("io.sentry.systemtest*") } + filter { + includeTestsMatching("io.sentry.systemtest*") + if (!includeGraphql) { + excludeTestsMatching("io.sentry.systemtest.Graphql*") + } + } } tasks.named("test").configure { diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java b/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java index cd69d85400..4bd6bb77bf 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java +++ b/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java @@ -1,6 +1,7 @@ package io.sentry.samples.spring.boot; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; @@ -17,6 +18,10 @@ @RequestMapping("/tracing/") public class DistributedTracingController { private static final Logger LOGGER = LoggerFactory.getLogger(DistributedTracingController.class); + private static final String BASIC_AUTH = + "Basic " + + Base64.getEncoder().encodeToString("user:password".getBytes(StandardCharsets.UTF_8)); + private final WebClient webClient; public DistributedTracingController(WebClient webClient) { @@ -28,9 +33,7 @@ Mono person(@PathVariable Long id) { return webClient .get() .uri("http://localhost:8080/person/{id}", id) - .header( - HttpHeaders.AUTHORIZATION, - "Basic " + HttpHeaders.encodeBasicAuth("user", "password", Charset.defaultCharset())) + .header(HttpHeaders.AUTHORIZATION, BASIC_AUTH) .retrieve() .bodyToMono(Person.class) .map(response -> response); @@ -41,9 +44,7 @@ Mono create(@RequestBody Person person) { return webClient .post() .uri("http://localhost:8080/person/") - .header( - HttpHeaders.AUTHORIZATION, - "Basic " + HttpHeaders.encodeBasicAuth("user", "password", Charset.defaultCharset())) + .header(HttpHeaders.AUTHORIZATION, BASIC_AUTH) .body(Mono.just(person), Person.class) .retrieve() .bodyToMono(Person.class) diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/PersonService.java b/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/PersonService.java index ed7422d9d0..ed821f6925 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/PersonService.java +++ b/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/PersonService.java @@ -11,7 +11,7 @@ public class PersonService { Mono create(Person person) { return Mono.delay(Duration.ofMillis(100)) - .publishOn(Schedulers.boundedElastic()) + .publishOn(Schedulers.elastic()) .doOnNext(__ -> Sentry.captureMessage("Creating person")) .map(__ -> person); } diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index cc535c725e..fa18e983e0 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -15,21 +15,31 @@ group = "io.sentry.sample.spring-boot" version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = JavaVersion.VERSION_17 +java.sourceCompatibility = JavaVersion.VERSION_11 -java.targetCompatibility = JavaVersion.VERSION_17 +java.targetCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() } +fun springBoot2SupportsGraphql(): Boolean { + val version = libs.versions.springboot2.get().removeSuffix(".RELEASE") + val parts = version.split(".").map { it.toIntOrNull() ?: 0 } + val major = parts.getOrElse(0) { 0 } + val minor = parts.getOrElse(1) { 0 } + return major > 2 || (major == 2 && minor >= 7) +} + +val includeGraphql = !project.hasProperty("excludeGraphql") && springBoot2SupportsGraphql() + configure { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } tasks.withType().configureEach { kotlin { compilerOptions.freeCompilerArgs = listOf("-Xjsr305=strict") - compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 } } @@ -38,7 +48,9 @@ dependencies { implementation(libs.springboot.starter) implementation(libs.springboot.starter.actuator) implementation(libs.springboot.starter.aop) - implementation(libs.springboot.starter.graphql) + if (includeGraphql) { + implementation(libs.springboot.starter.graphql) + } implementation(libs.springboot.starter.jdbc) implementation(libs.springboot.starter.quartz) implementation(libs.springboot.starter.security) @@ -56,7 +68,9 @@ dependencies { implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) implementation(projects.sentrySpringBootStarter) implementation(projects.sentryLogback) - implementation(projects.sentryGraphql) + if (includeGraphql) { + implementation(projects.sentryGraphql) + } implementation(projects.sentryQuartz) implementation(projects.sentryAsyncProfiler) @@ -102,7 +116,15 @@ tasks.jar { tasks.startScripts { dependsOn(tasks.shadowJar) } -configure { test { java.srcDir("src/test/java") } } +configure { + main { + if (!includeGraphql) { + java.exclude("**/graphql/**") + resources.exclude("graphql/**") + } + } + test { java.srcDir("src/test/java") } +} tasks.register("systemTest").configure { group = "verification" @@ -120,7 +142,12 @@ tasks.register("systemTest").configure { minHeapSize = "128m" maxHeapSize = "1g" - filter { includeTestsMatching("io.sentry.systemtest*") } + filter { + includeTestsMatching("io.sentry.systemtest*") + if (!includeGraphql) { + excludeTestsMatching("io.sentry.systemtest.Graphql*") + } + } } tasks.named("test").configure { diff --git a/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts index 319431e71d..0f2743be5d 100644 --- a/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts @@ -1,9 +1,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import org.springframework.boot.gradle.plugin.SpringBootPlugin plugins { application - alias(libs.plugins.springboot3) apply false alias(libs.plugins.spring.dependency.management) alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.spring) @@ -31,7 +29,7 @@ extra["kotlin-coroutines.version"] = "1.9.0" dependencyManagement { imports { - mavenBom(SpringBootPlugin.BOM_COORDINATES) + mavenBom("org.springframework.boot:spring-boot-dependencies:${libs.versions.springboot3.get()}") mavenBom(libs.kotlin.bom.get().toString()) } } @@ -57,7 +55,7 @@ dependencies { testImplementation(projects.sentrySystemTestSupport) testImplementation(libs.kotlin.test.junit) - testImplementation(libs.springboot.starter.test) { + testImplementation(libs.springboot3.starter.test) { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } } diff --git a/sentry-spring-boot/build.gradle.kts b/sentry-spring-boot/build.gradle.kts index 74f5d7c87b..0f48809a0a 100644 --- a/sentry-spring-boot/build.gradle.kts +++ b/sentry-spring-boot/build.gradle.kts @@ -35,7 +35,7 @@ dependencies { compileOnly(libs.servlet.api) compileOnly(libs.springboot.starter) compileOnly(libs.springboot.starter.aop) - compileOnly(libs.springboot.starter.graphql) + compileOnly(libs.spring.graphql) compileOnly(libs.springboot.starter.quartz) compileOnly(libs.springboot.starter.security) compileOnly(libs.spring.kafka2) diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java index c7d5a892e9..f89f5c5bb3 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java @@ -1,7 +1,6 @@ package io.sentry.spring.boot; import com.jakewharton.nopen.annotation.Open; -import graphql.GraphQLError; import io.sentry.EventProcessor; import io.sentry.IScopes; import io.sentry.ISpanFactory; @@ -12,7 +11,6 @@ import io.sentry.Sentry; import io.sentry.SentryIntegrationPackageStorage; import io.sentry.SentryOptions; -import io.sentry.graphql.SentryGraphqlExceptionHandler; import io.sentry.protocol.SdkVersion; import io.sentry.quartz.SentryJobListener; import io.sentry.spring.ContextTagsEventProcessor; @@ -75,7 +73,6 @@ import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; -import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.client.RestTemplate; @@ -203,11 +200,12 @@ static class ContextTagsEventProcessorConfiguration { @Configuration(proxyBeanMethods = false) @Import(SentryGraphqlAutoConfiguration.class) @Open - @ConditionalOnClass({ - SentryGraphqlExceptionHandler.class, - DataFetcherExceptionResolverAdapter.class, - GraphQLError.class - }) + @ConditionalOnClass( + name = { + "io.sentry.graphql.SentryGraphqlExceptionHandler", + "org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter", + "graphql.GraphQLError" + }) static class GraphqlConfiguration {} @Configuration(proxyBeanMethods = false) diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentrySpringVersionChecker.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentrySpringVersionChecker.java index 1cbcb4f090..2da1a3dd8d 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentrySpringVersionChecker.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentrySpringVersionChecker.java @@ -14,7 +14,8 @@ final class SentrySpringVersionChecker @Override public void onApplicationEvent(ApplicationContextInitializedEvent event) { - if (!SpringBootVersion.getVersion().startsWith("2")) { + String springBootVersion = SpringBootVersion.getVersion(); + if (springBootVersion != null && !springBootVersion.startsWith("2")) { logger.warn("############################### WARNING ###############################"); logger.warn("## ##"); logger.warn("## !Incompatible Spring Boot Version detected! ##"); diff --git a/sentry-spring/build.gradle.kts b/sentry-spring/build.gradle.kts index c4c75cb5f0..6b75b8d925 100644 --- a/sentry-spring/build.gradle.kts +++ b/sentry-spring/build.gradle.kts @@ -34,7 +34,7 @@ dependencies { compileOnly(libs.otel) compileOnly(libs.servlet.api) compileOnly(libs.slf4j.api) - compileOnly(libs.springboot.starter.graphql) + compileOnly(libs.spring.graphql) compileOnly(libs.springboot.starter.quartz) compileOnly(libs.spring.kafka2) compileOnly(projects.sentryOpentelemetry.sentryOpentelemetryAgentcustomization) diff --git a/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryWebFilter.java b/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryWebFilter.java index 03333d9541..0eb694421f 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryWebFilter.java +++ b/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryWebFilter.java @@ -153,7 +153,8 @@ private void finishTransaction(ServerWebExchange exchange, ITransaction transact } final @Nullable ServerHttpResponse response = exchange.getResponse(); if (response != null) { - final @Nullable Integer rawStatusCode = response.getRawStatusCode(); + final @Nullable Integer rawStatusCode = + response.getStatusCode() != null ? response.getStatusCode().value() : null; if (rawStatusCode != null) { transaction .getContexts() From b8a9c0bde830a50552fd36d1590c191d8b93b0bb Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 11 May 2026 15:03:24 +0200 Subject: [PATCH 4/9] fix(spring): Avoid deprecated Reactor scheduler in sample Remove the explicit elastic scheduler from the Spring Boot WebFlux sample. Mono.delay already schedules the delayed work, and using Schedulers.elastic triggers deprecation warnings that fail CI under -Werror. Co-Authored-By: Claude --- .../main/java/io/sentry/samples/spring/boot/PersonService.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/PersonService.java b/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/PersonService.java index ed821f6925..4a9ae98a44 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/PersonService.java +++ b/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/PersonService.java @@ -4,14 +4,12 @@ import java.time.Duration; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; @Service public class PersonService { Mono create(Person person) { return Mono.delay(Duration.ofMillis(100)) - .publishOn(Schedulers.elastic()) .doOnNext(__ -> Sentry.captureMessage("Creating person")) .map(__ -> person); } From 87b8bc3f8a5e112287c9d9eb36d2c07941e623a8 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Tue, 12 May 2026 06:11:51 +0200 Subject: [PATCH 5/9] fix(spring): Exclude Kafka from old Boot 2 matrix Spring Kafka sample support depends on newer Spring Boot 2 dependency management. Exclude Kafka sources, profile startup, and system tests when the matrix runs Boot 2 versions before 2.7. Keep the system test classpath aligned with the SDK test helpers by importing the OkHttp and Jackson BOMs after the tested Spring Boot BOMs. Co-Authored-By: Claude --- .github/workflows/spring-boot-2-matrix.yml | 1 + .../build.gradle.kts | 20 ++++++++++++++----- .../build.gradle.kts | 20 ++++++++++++++----- .../build.gradle.kts | 20 ++++++++++++++----- .../build.gradle.kts | 1 + .../sentry-samples-spring/build.gradle.kts | 1 + test/system-test-runner.py | 7 +++++-- 7 files changed, 53 insertions(+), 17 deletions(-) diff --git a/.github/workflows/spring-boot-2-matrix.yml b/.github/workflows/spring-boot-2-matrix.yml index 250ce41652..2b87902ab4 100644 --- a/.github/workflows/spring-boot-2-matrix.yml +++ b/.github/workflows/spring-boot-2-matrix.yml @@ -68,6 +68,7 @@ jobs: fi if [[ ! "$springboot_version" =~ ^2\.7\. ]]; then echo "ORG_GRADLE_PROJECT_excludeGraphql=true" >> "$GITHUB_ENV" + echo "ORG_GRADLE_PROJECT_excludeKafka=true" >> "$GITHUB_ENV" fi sed -i 's/^\(springboot2[[:space:]]*=[[:space:]]*\)".*"/\1"'"$springboot_version"'"/' gradle/libs.versions.toml echo "Updated Spring Boot 2.x version to $springboot_version" diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index 4bd5fe6b37..63b97a46e1 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -21,7 +21,7 @@ java.targetCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() } -fun springBoot2SupportsGraphql(): Boolean { +fun springBoot2SupportsOptionalIntegrations(): Boolean { val version = libs.versions.springboot2.get().removeSuffix(".RELEASE") val parts = version.split(".").map { it.toIntOrNull() ?: 0 } val major = parts.getOrElse(0) { 0 } @@ -29,7 +29,9 @@ fun springBoot2SupportsGraphql(): Boolean { return major > 2 || (major == 2 && minor >= 7) } -val includeGraphql = !project.hasProperty("excludeGraphql") && springBoot2SupportsGraphql() +val includeGraphql = + !project.hasProperty("excludeGraphql") && springBoot2SupportsOptionalIntegrations() +val includeKafka = !project.hasProperty("excludeKafka") && springBoot2SupportsOptionalIntegrations() configure { sourceCompatibility = JavaVersion.VERSION_11 @@ -74,9 +76,10 @@ dependencies { implementation(projects.sentryOpentelemetry.sentryOpentelemetryAgentlessSpring) implementation(projects.sentryAsyncProfiler) - // kafka - implementation(libs.spring.kafka2) - implementation(projects.sentryKafka) + if (includeKafka) { + implementation(libs.spring.kafka2) + implementation(projects.sentryKafka) + } // database query tracing implementation(projects.sentryJdbc) @@ -123,6 +126,10 @@ configure { java.exclude("**/graphql/**") resources.exclude("graphql/**") } + if (!includeKafka) { + java.exclude("**/queues/kafka/**") + resources.exclude("application-kafka.properties") + } } test { java.srcDir("src/test/java") } } @@ -148,6 +155,9 @@ tasks.register("systemTest").configure { if (!includeGraphql) { excludeTestsMatching("io.sentry.systemtest.Graphql*") } + if (!includeKafka) { + excludeTestsMatching("io.sentry.systemtest.Kafka*") + } } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index 563ae56b07..a1e260cc30 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -21,7 +21,7 @@ java.targetCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() } -fun springBoot2SupportsGraphql(): Boolean { +fun springBoot2SupportsOptionalIntegrations(): Boolean { val version = libs.versions.springboot2.get().removeSuffix(".RELEASE") val parts = version.split(".").map { it.toIntOrNull() ?: 0 } val major = parts.getOrElse(0) { 0 } @@ -29,7 +29,9 @@ fun springBoot2SupportsGraphql(): Boolean { return major > 2 || (major == 2 && minor >= 7) } -val includeGraphql = !project.hasProperty("excludeGraphql") && springBoot2SupportsGraphql() +val includeGraphql = + !project.hasProperty("excludeGraphql") && springBoot2SupportsOptionalIntegrations() +val includeKafka = !project.hasProperty("excludeKafka") && springBoot2SupportsOptionalIntegrations() configure { sourceCompatibility = JavaVersion.VERSION_11 @@ -70,9 +72,10 @@ dependencies { implementation(projects.sentryAsyncProfiler) implementation(libs.otel) - // kafka - implementation(libs.spring.kafka2) - implementation(projects.sentryKafka) + if (includeKafka) { + implementation(libs.spring.kafka2) + implementation(projects.sentryKafka) + } // database query tracing implementation(projects.sentryJdbc) @@ -119,6 +122,10 @@ configure { java.exclude("**/graphql/**") resources.exclude("graphql/**") } + if (!includeKafka) { + java.exclude("**/queues/kafka/**") + resources.exclude("application-kafka.properties") + } } test { java.srcDir("src/test/java") } } @@ -168,6 +175,9 @@ tasks.register("systemTest").configure { if (!includeGraphql) { excludeTestsMatching("io.sentry.systemtest.Graphql*") } + if (!includeKafka) { + excludeTestsMatching("io.sentry.systemtest.Kafka*") + } } } diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index fa18e983e0..a286583b66 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -21,7 +21,7 @@ java.targetCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() } -fun springBoot2SupportsGraphql(): Boolean { +fun springBoot2SupportsOptionalIntegrations(): Boolean { val version = libs.versions.springboot2.get().removeSuffix(".RELEASE") val parts = version.split(".").map { it.toIntOrNull() ?: 0 } val major = parts.getOrElse(0) { 0 } @@ -29,7 +29,9 @@ fun springBoot2SupportsGraphql(): Boolean { return major > 2 || (major == 2 && minor >= 7) } -val includeGraphql = !project.hasProperty("excludeGraphql") && springBoot2SupportsGraphql() +val includeGraphql = + !project.hasProperty("excludeGraphql") && springBoot2SupportsOptionalIntegrations() +val includeKafka = !project.hasProperty("excludeKafka") && springBoot2SupportsOptionalIntegrations() configure { sourceCompatibility = JavaVersion.VERSION_11 @@ -60,9 +62,10 @@ dependencies { implementation(libs.springboot.starter.websocket) implementation(libs.caffeine) - // kafka - implementation(libs.spring.kafka2) - implementation(projects.sentryKafka) + if (includeKafka) { + implementation(libs.spring.kafka2) + implementation(projects.sentryKafka) + } implementation(Config.Libs.aspectj) implementation(Config.Libs.kotlinReflect) implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) @@ -122,6 +125,10 @@ configure { java.exclude("**/graphql/**") resources.exclude("graphql/**") } + if (!includeKafka) { + java.exclude("**/queues/kafka/**") + resources.exclude("application-kafka.properties") + } } test { java.srcDir("src/test/java") } } @@ -147,6 +154,9 @@ tasks.register("systemTest").configure { if (!includeGraphql) { excludeTestsMatching("io.sentry.systemtest.Graphql*") } + if (!includeKafka) { + excludeTestsMatching("io.sentry.systemtest.Kafka*") + } } } diff --git a/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts index 0f2743be5d..c8ef0a35f1 100644 --- a/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts @@ -31,6 +31,7 @@ dependencyManagement { imports { mavenBom("org.springframework.boot:spring-boot-dependencies:${libs.versions.springboot3.get()}") mavenBom(libs.kotlin.bom.get().toString()) + mavenBom(libs.jackson.bom.get().toString()) } } diff --git a/sentry-samples/sentry-samples-spring/build.gradle.kts b/sentry-samples/sentry-samples-spring/build.gradle.kts index 446baf3a69..4041bde40b 100644 --- a/sentry-samples/sentry-samples-spring/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring/build.gradle.kts @@ -33,6 +33,7 @@ dependencyManagement { mavenBom(libs.springboot2.bom.get().toString()) mavenBom(libs.kotlin.bom.get().toString()) mavenBom(libs.jackson.bom.get().toString()) + mavenBom(libs.okhttp.bom.get().toString()) } } diff --git a/test/system-test-runner.py b/test/system-test-runner.py index 784448715e..7dd7530c8b 100644 --- a/test/system-test-runner.py +++ b/test/system-test-runner.py @@ -224,11 +224,14 @@ def kill_process(self, pid: int, name: str) -> None: except (OSError, ProcessLookupError): print(f"Process {pid} was already dead") + def exclude_kafka(self) -> bool: + return os.environ.get("ORG_GRADLE_PROJECT_excludeKafka") == "true" + def module_requires_kafka(self, sample_module: str) -> bool: - return sample_module in KAFKA_BROKER_REQUIRED_MODULES + return not self.exclude_kafka() and sample_module in KAFKA_BROKER_REQUIRED_MODULES def module_requires_kafka_profile(self, sample_module: str) -> bool: - return sample_module in KAFKA_PROFILE_REQUIRED_MODULES + return not self.exclude_kafka() and sample_module in KAFKA_PROFILE_REQUIRED_MODULES def wait_for_port(self, host: str, port: int, max_attempts: int = 20) -> bool: for _ in range(max_attempts): From 16dd5574531da2d80bfd4965b10b7508aab9a347 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Tue, 12 May 2026 06:53:15 +0200 Subject: [PATCH 6/9] fix(spring): Support older Reactor WebFlux APIs Spring Boot 2.1 and 2.2 use Reactor versions without Mono.doFirst or Schedulers.onScheduleHook. Avoid those calls in the Boot 2 WebFlux integration so old matrix jobs can start and serve requests. Co-Authored-By: Claude --- .../boot/SentryWebfluxAutoConfiguration.java | 14 +++- .../spring/webflux/SentryWebFilter.java | 65 ++++++++++--------- 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryWebfluxAutoConfiguration.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryWebfluxAutoConfiguration.java index e7f6a444b8..db09180b1c 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryWebfluxAutoConfiguration.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryWebfluxAutoConfiguration.java @@ -6,6 +6,7 @@ import io.sentry.spring.webflux.SentryScheduleHook; import io.sentry.spring.webflux.SentryWebExceptionHandler; import io.sentry.spring.webflux.SentryWebFilter; +import java.util.function.Function; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.springframework.boot.ApplicationRunner; @@ -32,10 +33,21 @@ public class SentryWebfluxAutoConfiguration { @Bean public @NotNull ApplicationRunner sentryScheduleHookApplicationRunner() { return args -> { - Schedulers.onScheduleHook("sentry", new SentryScheduleHook()); + if (hasOnScheduleHook()) { + Schedulers.onScheduleHook("sentry", new SentryScheduleHook()); + } }; } + private static boolean hasOnScheduleHook() { + try { + Schedulers.class.getMethod("onScheduleHook", String.class, Function.class); + return true; + } catch (NoSuchMethodException ignored) { + return false; + } + } + /** Configures a filter that sets up Sentry {@link IScope} for each request. */ @Bean @Order(SENTRY_SPRING_FILTER_PRECEDENCE) diff --git a/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryWebFilter.java b/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryWebFilter.java index 0eb694421f..5f7cb8bab1 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryWebFilter.java +++ b/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryWebFilter.java @@ -75,38 +75,39 @@ isTracingEnabled && shouldTraceRequest(requestScopes, request) ? startTransaction(requestScopes, request, transactionContext) : null; - return webFilterChain - .filter(serverWebExchange) - .doFinally( - __ -> { - if (transaction != null) { - finishTransaction(serverWebExchange, transaction); - } - Sentry.setCurrentScopes(NoOpScopes.getInstance()); - }) - .doOnError( - e -> { - if (transaction != null) { - transaction.setStatus(SpanStatus.INTERNAL_ERROR); - transaction.setThrowable(e); - } - }) - .doFirst( - () -> { - serverWebExchange.getAttributes().put(SENTRY_SCOPES_KEY, requestScopes); - Sentry.setCurrentScopes(requestScopes); - final ServerHttpResponse response = serverWebExchange.getResponse(); - - final Hint hint = new Hint(); - hint.set(WEBFLUX_FILTER_REQUEST, request); - hint.set(WEBFLUX_FILTER_RESPONSE, response); - final String methodName = - request.getMethod() != null ? request.getMethod().name() : "unknown"; - requestScopes.addBreadcrumb( - Breadcrumb.http(request.getURI().toString(), methodName), hint); - requestScopes.configureScope( - scope -> scope.setRequest(sentryRequestResolver.resolveSentryRequest(request))); - }); + return Mono.defer( + () -> { + serverWebExchange.getAttributes().put(SENTRY_SCOPES_KEY, requestScopes); + Sentry.setCurrentScopes(requestScopes); + final ServerHttpResponse response = serverWebExchange.getResponse(); + + final Hint hint = new Hint(); + hint.set(WEBFLUX_FILTER_REQUEST, request); + hint.set(WEBFLUX_FILTER_RESPONSE, response); + final String methodName = + request.getMethod() != null ? request.getMethod().name() : "unknown"; + requestScopes.addBreadcrumb( + Breadcrumb.http(request.getURI().toString(), methodName), hint); + requestScopes.configureScope( + scope -> scope.setRequest(sentryRequestResolver.resolveSentryRequest(request))); + + return webFilterChain + .filter(serverWebExchange) + .doFinally( + __ -> { + if (transaction != null) { + finishTransaction(serverWebExchange, transaction); + } + Sentry.setCurrentScopes(NoOpScopes.getInstance()); + }) + .doOnError( + e -> { + if (transaction != null) { + transaction.setStatus(SpanStatus.INTERNAL_ERROR); + transaction.setThrowable(e); + } + }); + }); } private boolean isIgnored(final @NotNull IScopes scopes) { From 1137d604a5767a57b9ed9b43a9e90e02bb5e893b Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Tue, 12 May 2026 08:31:25 +0200 Subject: [PATCH 7/9] ci(spring): Skip OTel no-agent sample on old Boot 2 Spring Boot 2.1 and 2.2 cannot parse newer OpenTelemetry auto-configuration classes during startup. Keep the matrix coverage for supported samples and skip the no-agent OTel sample for those versions. Co-Authored-By: Claude --- .github/workflows/spring-boot-2-matrix.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/spring-boot-2-matrix.yml b/.github/workflows/spring-boot-2-matrix.yml index 2b87902ab4..a076e0d5e6 100644 --- a/.github/workflows/spring-boot-2-matrix.yml +++ b/.github/workflows/spring-boot-2-matrix.yml @@ -141,6 +141,7 @@ jobs: --build "true" - name: Test sentry-samples-spring-boot-opentelemetry-noagent + if: ${{ !startsWith(matrix.springboot-version, '2.1.') && !startsWith(matrix.springboot-version, '2.2.') }} run: | python3 test/system-test-runner.py test \ --module "sentry-samples-spring-boot-opentelemetry-noagent" \ From 1fa2c8ce1d41819e5832febaae9d430e579b57cb Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Tue, 12 May 2026 13:13:00 +0200 Subject: [PATCH 8/9] ci(spring): Drop old Boot 2 matrix versions Remove Spring Boot 2.1 and 2.2 from the matrix instead of carrying WebFlux compatibility changes for their older Reactor and Spring APIs. Restore the WebFlux filter implementation now that those versions are no longer tested. Co-Authored-By: Claude --- .github/workflows/spring-boot-2-matrix.yml | 6 +- .../spring/webflux/SentryWebFilter.java | 68 +++++++++---------- 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/.github/workflows/spring-boot-2-matrix.yml b/.github/workflows/spring-boot-2-matrix.yml index a076e0d5e6..6a0a142b4f 100644 --- a/.github/workflows/spring-boot-2-matrix.yml +++ b/.github/workflows/spring-boot-2-matrix.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - springboot-version: [ '2.1.0', '2.2.5', '2.4.13', '2.5.15', '2.6.15', '2.7.0', '2.7.18' ] + springboot-version: [ '2.4.13', '2.5.15', '2.6.15', '2.7.0', '2.7.18' ] name: Spring Boot ${{ matrix.springboot-version }} env: @@ -63,9 +63,6 @@ jobs: - name: Update Spring Boot 2.x version run: | springboot_version="${{ matrix.springboot-version }}" - if [[ "$springboot_version" =~ ^2\.[0-3]\. ]]; then - springboot_version="${springboot_version}.RELEASE" - fi if [[ ! "$springboot_version" =~ ^2\.7\. ]]; then echo "ORG_GRADLE_PROJECT_excludeGraphql=true" >> "$GITHUB_ENV" echo "ORG_GRADLE_PROJECT_excludeKafka=true" >> "$GITHUB_ENV" @@ -141,7 +138,6 @@ jobs: --build "true" - name: Test sentry-samples-spring-boot-opentelemetry-noagent - if: ${{ !startsWith(matrix.springboot-version, '2.1.') && !startsWith(matrix.springboot-version, '2.2.') }} run: | python3 test/system-test-runner.py test \ --module "sentry-samples-spring-boot-opentelemetry-noagent" \ diff --git a/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryWebFilter.java b/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryWebFilter.java index 5f7cb8bab1..03333d9541 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryWebFilter.java +++ b/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryWebFilter.java @@ -75,39 +75,38 @@ isTracingEnabled && shouldTraceRequest(requestScopes, request) ? startTransaction(requestScopes, request, transactionContext) : null; - return Mono.defer( - () -> { - serverWebExchange.getAttributes().put(SENTRY_SCOPES_KEY, requestScopes); - Sentry.setCurrentScopes(requestScopes); - final ServerHttpResponse response = serverWebExchange.getResponse(); - - final Hint hint = new Hint(); - hint.set(WEBFLUX_FILTER_REQUEST, request); - hint.set(WEBFLUX_FILTER_RESPONSE, response); - final String methodName = - request.getMethod() != null ? request.getMethod().name() : "unknown"; - requestScopes.addBreadcrumb( - Breadcrumb.http(request.getURI().toString(), methodName), hint); - requestScopes.configureScope( - scope -> scope.setRequest(sentryRequestResolver.resolveSentryRequest(request))); - - return webFilterChain - .filter(serverWebExchange) - .doFinally( - __ -> { - if (transaction != null) { - finishTransaction(serverWebExchange, transaction); - } - Sentry.setCurrentScopes(NoOpScopes.getInstance()); - }) - .doOnError( - e -> { - if (transaction != null) { - transaction.setStatus(SpanStatus.INTERNAL_ERROR); - transaction.setThrowable(e); - } - }); - }); + return webFilterChain + .filter(serverWebExchange) + .doFinally( + __ -> { + if (transaction != null) { + finishTransaction(serverWebExchange, transaction); + } + Sentry.setCurrentScopes(NoOpScopes.getInstance()); + }) + .doOnError( + e -> { + if (transaction != null) { + transaction.setStatus(SpanStatus.INTERNAL_ERROR); + transaction.setThrowable(e); + } + }) + .doFirst( + () -> { + serverWebExchange.getAttributes().put(SENTRY_SCOPES_KEY, requestScopes); + Sentry.setCurrentScopes(requestScopes); + final ServerHttpResponse response = serverWebExchange.getResponse(); + + final Hint hint = new Hint(); + hint.set(WEBFLUX_FILTER_REQUEST, request); + hint.set(WEBFLUX_FILTER_RESPONSE, response); + final String methodName = + request.getMethod() != null ? request.getMethod().name() : "unknown"; + requestScopes.addBreadcrumb( + Breadcrumb.http(request.getURI().toString(), methodName), hint); + requestScopes.configureScope( + scope -> scope.setRequest(sentryRequestResolver.resolveSentryRequest(request))); + }); } private boolean isIgnored(final @NotNull IScopes scopes) { @@ -154,8 +153,7 @@ private void finishTransaction(ServerWebExchange exchange, ITransaction transact } final @Nullable ServerHttpResponse response = exchange.getResponse(); if (response != null) { - final @Nullable Integer rawStatusCode = - response.getStatusCode() != null ? response.getStatusCode().value() : null; + final @Nullable Integer rawStatusCode = response.getRawStatusCode(); if (rawStatusCode != null) { transaction .getContexts() From 81e6e98c383fb6e37c3e82f6bbf2fb3817f55f96 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Tue, 12 May 2026 14:38:10 +0200 Subject: [PATCH 9/9] fix(spring): Restore WebFlux schedule hook registration Revert the compatibility guard for Reactor versions that are no longer covered by the Spring Boot matrix. Co-Authored-By: Claude --- .../boot/SentryWebfluxAutoConfiguration.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryWebfluxAutoConfiguration.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryWebfluxAutoConfiguration.java index db09180b1c..e7f6a444b8 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryWebfluxAutoConfiguration.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryWebfluxAutoConfiguration.java @@ -6,7 +6,6 @@ import io.sentry.spring.webflux.SentryScheduleHook; import io.sentry.spring.webflux.SentryWebExceptionHandler; import io.sentry.spring.webflux.SentryWebFilter; -import java.util.function.Function; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.springframework.boot.ApplicationRunner; @@ -33,21 +32,10 @@ public class SentryWebfluxAutoConfiguration { @Bean public @NotNull ApplicationRunner sentryScheduleHookApplicationRunner() { return args -> { - if (hasOnScheduleHook()) { - Schedulers.onScheduleHook("sentry", new SentryScheduleHook()); - } + Schedulers.onScheduleHook("sentry", new SentryScheduleHook()); }; } - private static boolean hasOnScheduleHook() { - try { - Schedulers.class.getMethod("onScheduleHook", String.class, Function.class); - return true; - } catch (NoSuchMethodException ignored) { - return false; - } - } - /** Configures a filter that sets up Sentry {@link IScope} for each request. */ @Bean @Order(SENTRY_SPRING_FILTER_PRECEDENCE)