From 104267513dbb4536157fb15763bbd1f437080fa1 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 6 May 2026 13:29:08 +0200 Subject: [PATCH 01/10] 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 38aaacec27a..ca14fdc419d 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 629535e282d..00635e72549 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 bbd4f986d96..aa4671cdab1 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 02/10] 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 ca14fdc419d..e9fa9939f10 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 00635e72549..4572d43409c 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 03/10] 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 e9fa9939f10..250ce41652a 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 4572d43409c..930b1f43386 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 ab39c981b44..10f5e665b81 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 ed0af32b031..7137a71d8b3 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 d3d66c469b7..1bf1fca2058 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 ae3ef70ad70..2bb95b9b413 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 f1665f513d1..4bd5fe6b374 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 7c84875ca07..563ae56b079 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 d5b04543576..76e29fb87c9 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 b10b30737d8..08a98ae1665 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 cd69d854006..4bd6bb77bfb 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 ed7422d9d0b..ed821f69252 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 cc535c725e1..fa18e983e0d 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 319431e71d2..0f2743be5d8 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 74f5d7c87bb..0f48809a0a7 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 c7d5a892e9f..f89f5c5bb31 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 1cbcb4f090c..2da1a3dd8d9 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 c4c75cb5f07..6b75b8d925a 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 03333d95417..0eb694421f3 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 04/10] 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 ed821f69252..4a9ae98a447 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 05/10] 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 250ce41652a..2b87902ab4f 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 4bd5fe6b374..63b97a46e12 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 563ae56b079..a1e260cc307 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 fa18e983e0d..a286583b66a 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 0f2743be5d8..c8ef0a35f19 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 446baf3a696..4041bde40b4 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 784448715e9..7dd7530c8bd 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 06/10] 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 e7f6a444b87..db09180b1c8 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 0eb694421f3..5f7cb8bab17 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 07/10] 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 2b87902ab4f..a076e0d5e63 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 08/10] 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 a076e0d5e63..6a0a142b4ff 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 5f7cb8bab17..03333d95417 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 09/10] 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 db09180b1c8..e7f6a444b87 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) From 37d04c6b5979f2939603aa60a686bf4874f5484a Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 15 May 2026 10:18:32 +0200 Subject: [PATCH 10/10] fix(spring): Support older Spring GraphQL options API Use the erased Consumer signature in the batch loader registry wrapper so the code compiles with both Spring GraphQL 1.2/1.3 and 1.4. Let the Spring Boot 3 Gradle plugin follow the tested matrix version instead of pinning it separately. Co-Authored-By: Claude --- gradle/libs.versions.toml | 3 +-- .../spring/jakarta/graphql/SentryBatchLoaderRegistry.java | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 10f5e665b81..6012ad6153f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,7 +32,6 @@ 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" @@ -62,7 +61,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 = "springboot3Plugin" } +springboot3 = { id = "org.springframework.boot", version.ref = "springboot3" } 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" } diff --git a/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/graphql/SentryBatchLoaderRegistry.java b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/graphql/SentryBatchLoaderRegistry.java index a75aa281349..4e7c3665aae 100644 --- a/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/graphql/SentryBatchLoaderRegistry.java +++ b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/graphql/SentryBatchLoaderRegistry.java @@ -75,8 +75,8 @@ public BatchLoaderRegistry.RegistrationSpec withName(String name) { } @Override - public BatchLoaderRegistry.RegistrationSpec withOptions( - Consumer optionsConsumer) { + @SuppressWarnings({"rawtypes", "unchecked"}) + public BatchLoaderRegistry.RegistrationSpec withOptions(Consumer optionsConsumer) { return delegate.withOptions(optionsConsumer); }