Skip to content

Enable Gradle enhanced graph ordering - #12326

Closed
AlexeyKuznetsov-DD wants to merge 2 commits into
masterfrom
alexeyk/gradle-enhanced-graph-ordering
Closed

Enable Gradle enhanced graph ordering#12326
AlexeyKuznetsov-DD wants to merge 2 commits into
masterfrom
alexeyk/gradle-enhanced-graph-ordering

Conversation

@AlexeyKuznetsov-DD

@AlexeyKuznetsov-DD AlexeyKuznetsov-DD commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

What Does This Do

  • Enables Gradle 9.7's ENHANCED_GRAPH_ORDERING feature preview.
  • Keeps the SofaRPC Netty alignment constraint because enhanced ordering changes which duplicate class wins but cannot reconcile classes embedded in an unrelated fat JAR.
  • Replaces MockServer's JUnit integration artifact with mockserver-netty-no-dependencies:5.14.0 and a minimal local JUnit extension, avoiding embedded JUnit and incompatible SLF4J classes.
  • Excludes the tracer's unrelocated OkHttp and Okio forks from OpenAI test runtime classpaths, where they can shadow OpenAI's newer upstream versions.
  • Centralizes Spring Boot Shadow JAR configuration in a build-logic/spring-boot-shadow convention that merges service and Spring discovery metadata.
  • Preserves historical first-file Spring factories behavior in two legacy smoke fixtures that depend on it, while applying the shared convention to the remaining Spring Boot smoke applications.
  • Pins the Quartz 2.4 compatibility suite to Quartz 2.4.0 and its matching javax.transaction API, and refreshes ordering-sensitive dependency locks.
  • Extracts namespace-neutral Liberty classloader naming and multipart hooks into a shared module, while restricting servlet-specific advice to the matching javax or jakarta generation.

Motivation

Gradle 9.6 changed dependency traversal and could place an older fat JAR before newer modular dependencies. Gradle 9.7 addresses that traversal behavior behind ENHANCED_GRAPH_ORDERING; the preview becomes the default in Gradle 10.

Enabling the preview also reveals classpaths that previously depended on incidental traversal order. Some are normal dependency conflicts that can be made deterministic through exclusions or direct dependencies. Others involve classes or resources hidden inside fat/no-dependencies JARs, which Gradle cannot reconcile by module version because they belong to different coordinates.

SofaRPC is in the latter category: sofa-rpc-all:5.14.2 brings the class-carrying netty-all:4.1.44.Final, while gRPC brings individual Netty 4.1.79 modules. Enhanced ordering changes which copy of io.netty.** loads first, but does not eliminate the duplicate classes. Retaining the constraint on netty-all:4.1.79.Final replaces the fat JAR with Netty's class-free aggregator and an aligned modular dependency set.

The ordering change also exposed two structural issues. Spring Boot fat JARs normally need discovery metadata from every dependency merged rather than retaining an arbitrary duplicate, and Liberty 20 and Liberty 23 reuse IBM implementation class names even though their servlet APIs use different namespaces. The Spring convention makes metadata handling consistent for normal fixtures, while the Liberty common module removes an unnecessary cross-generation test dependency and namespace matchers prevent incompatible servlet advice from being applied.

Additional Notes

This is stacked on #12323 and should be reviewed and merged after it.

The SofaRPC regression can otherwise combine AbstractReferenceCountedByteBuf from Netty 4.1.79 with ReferenceCountUpdater from the 4.1.44 fat JAR and fail with a NoSuchMethodError. With enhanced ordering, the old fat JAR may instead win for all Netty classes, hiding that particular linkage error while silently testing the wrong Netty version. The retained constraint removes both outcomes.

MockServer's previous mockserver-junit-jupiter-no-dependencies artifact embeds an older JUnit Platform under the original packages. Enhanced ordering allowed those classes to shadow JUnit Platform 1.14.1, producing:

java.lang.NoSuchMethodError:
  org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder(...)

The replacement uses mockserver-netty-no-dependencies, which contains the server implementation without embedded JUnit classes, plus a small extension implementing only the lifecycle and parameter injection used by the HTTP test fixtures. Version 5.14.0 is intentional: its unrelocated SLF4J API matches this module's SLF4J 1.7 line and it ships no logging provider, whereas 5.15.0 embeds SLF4J 2.0 and a JUL provider.

The OpenAI test classpath contains both Datadog's forked OkHttp/Okio and OpenAI's newer upstream versions under identical Java packages. Runtime-only exclusions preserve the tested OpenAI versions and avoid failures such as:

java.lang.NoSuchFieldError:
  okhttp3.MediaType$Companion Companion

The Spring Boot Shadow convention merges META-INF/services, Spring handlers, schemas, tooling, and factories. It lives in a separate Java 17 build-logic module because Shadow 9's plugin API targets Java 17, while other build-logic modules still target Java 8. appsec/springboot and the legacy Spring Boot 1.5 IAST fixture intentionally keep raw Shadow behavior plus a direct spring-boot-autoconfigure dependency. Merging their complete factory sets changes established request/error behavior: the AppSec fixture stops observing expected blocking, and the Java 8/11 IAST fixture reports RIPEMD128 MessageDigest not available. The direct dependency makes their historical first-file selection deterministic under enhanced ordering.

The Quartz version40Test suite is pinned to Quartz 2.4.0 so it remains distinct from latestDepTest. Quartz 2.4 uses javax.transaction.UserTransaction; Quartz 2.5 and later use Jakarta Transactions.

Liberty 23 previously loaded the complete Liberty 20 instrumentation module in tests to reuse deployment classloader naming. The shared module now contains the namespace-neutral classloader naming and multipart filename hooks. Request and response finish instrumentations retain symmetric javax.servlet and jakarta.servlet structure matchers because the production agent packages both generations. As a diagnostic, both module suites also passed with those matchers temporarily disabled after the test dependency was decoupled, but that isolated test setup does not model both generations being loaded by the production agent.

Validation:

  • ./gradlew :components:http:http-api:check :components:http:http-api:forkedTest :dd-java-agent:instrumentation:openai-java:openai-java-3.0:test :dd-java-agent:instrumentation:openai-java:openai-java-3.0:forkedTest :dd-java-agent:instrumentation:openai-java:openai-java-3.0:latestDepTest :dd-java-agent:instrumentation:sofarpc:sofarpc-5.0:test :dd-java-agent:instrumentation:sofarpc:sofarpc-5.0:forkedTest :dd-java-agent:instrumentation:sofarpc:sofarpc-5.0:latestDepTest :dd-smoke-tests:spring-boot-2.5-webflux:test -PtestJvm=17 --rerun-tasks (197 tests passed, 0 failed; 3 intentionally disabled HTTP-provider tests skipped)
  • ./gradlew :components:http:http-api:compileTestFixturesJava :components:http:http-api:forkedTest -PtestJvm=11 --rerun-tasks
  • ./gradlew :components:http:http-api:compileTestFixturesJava :components:http:http-api:forkedTest -PtestJvm=25 --rerun-tasks
  • ./gradlew :dd-java-agent:instrumentation:quartz-2.0:test :dd-java-agent:instrumentation:quartz-2.0:version40Test :dd-java-agent:instrumentation:quartz-2.0:latestDepTest --rerun-tasks (4 tests passed in each suite)
  • ./gradlew :dd-java-agent:instrumentation:spring:spring-webflux:spring-webflux-5.0:iastTest --rerun-tasks (8 tests passed)
  • ./gradlew :dd-smoke-tests:spring-boot-2.4-webflux:test :dd-smoke-tests:springboot-mongo:test -PtestJvm=17 --rerun-tasks (3 tests passed in each suite)
  • ./gradlew :dd-smoke-tests:appsec:springboot:test -PtestJvm=17 --rerun-tasks
  • ./gradlew :dd-smoke-tests:springboot:test -PtestJvm=8 --rerun-tasks (211 tests passed, 0 failed)
  • The AppSec blocking regression test passed individually on Java 8, 17, and 25; the legacy IAST regression test passed individually on Java 8 and 11.
  • ./gradlew :dd-java-agent:instrumentation:liberty:liberty-20.0:test :dd-java-agent:instrumentation:liberty:liberty-20.0:forkedTest :dd-java-agent:instrumentation:liberty:liberty-23.0:test :dd-java-agent:instrumentation:liberty:liberty-23.0:forkedTest -PtestJvm=17 --rerun-tasks (527 tests passed, 0 failed)
  • ./gradlew :dd-java-agent:instrumentation:liberty:liberty-20.0:muzzle :dd-java-agent:instrumentation:liberty:liberty-23.0:muzzle (2 checks passed)
  • ./gradlew :dd-java-agent:instrumentation:liberty:liberty-common:build
  • ./gradlew -p build-logic :spring-boot-shadow:check
  • ./gradlew :dd-java-agent:shadowJar

Gradle issue: gradle/gradle#38057

Contributor Checklist

Jira ticket: [PROJ-IDENT]

@AlexeyKuznetsov-DD AlexeyKuznetsov-DD added tag: no release notes Changes to exclude from release notes type: refactoring comp: tooling Build & Tooling tag: ai generated Largely based on code generated by an AI or LLM labels Aug 27, 2026
@AlexeyKuznetsov-DD AlexeyKuznetsov-DD self-assigned this Aug 27, 2026
@AlexeyKuznetsov-DD
AlexeyKuznetsov-DD marked this pull request as ready for review August 27, 2026 20:42
@AlexeyKuznetsov-DD
AlexeyKuznetsov-DD requested review from a team as code owners August 27, 2026 20:42
@AlexeyKuznetsov-DD
AlexeyKuznetsov-DD requested review from ValentinZakharov and removed request for a team August 27, 2026 20:42

@datadog-datadog-prod-us1-2 datadog-datadog-prod-us1-2 Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Datadog Autotest: PASS

More details

The changes use established repository patterns for dependency exclusions, MockServer setup, and Spring metadata merge. The static review finds no concrete failure.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Datadog Autotest · Commit c6e34cd · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest

@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 59.05% (-0.01%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 5da36a4 | Docs | View more details | Give us feedback!

@dd-octo-sts

dd-octo-sts Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 14.79 s 14.69 s [-0.1%; +1.5%] (no difference)
startup:insecure-bank:tracing:Agent 13.65 s 13.67 s [-1.0%; +0.6%] (no difference)
startup:petclinic:appsec:Agent 17.02 s 16.82 s [+0.4%; +2.0%] (maybe worse)
startup:petclinic:iast:Agent 17.03 s 17.09 s [-1.1%; +0.3%] (no difference)
startup:petclinic:profiling:Agent 15.99 s 16.73 s [-9.9%; +1.1%] (unstable)
startup:petclinic:sca:Agent 16.98 s 16.57 s [+1.3%; +3.6%] (significantly worse)
startup:petclinic:tracing:Agent 16.05 s 16.39 s [-3.2%; -0.9%] (maybe better)

Commit: 5da36a46 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

@AlexeyKuznetsov-DD
AlexeyKuznetsov-DD force-pushed the alexeyk/gradle-enhanced-graph-ordering branch 2 times, most recently from bdb9131 to 88bf0ee Compare August 28, 2026 01:25
@AlexeyKuznetsov-DD
AlexeyKuznetsov-DD marked this pull request as draft August 28, 2026 01:49
Base automatically changed from alexeyk/upgrade-gradle-9.7.1 to master August 28, 2026 09:55
Avoid Maven Central fallback when proxy is configured

Co-authored-by: alexey.kuznetsov <alexey.kuznetsov@datadoghq.com>
@AlexeyKuznetsov-DD
AlexeyKuznetsov-DD force-pushed the alexeyk/gradle-enhanced-graph-ordering branch from 6cda9a3 to 5da36a4 Compare September 4, 2026 00:04

@bric3 bric3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That description !


I understand this is still draft, but ENHANCED_GRAPH_ORDERING is still a preview, and this is the very first version of Gradle that ships with it. I'd rather hold off a bit before enabling this. Especially with that much changes. Note some comments feels like over narrating the inferrence.

That being said, I think there are interesting change that should land separately, and that should work without the graph ordering toggle.

  • The spring boot smoke test convention, looks like the first thing I'll try to land separately. (Also, I'm suggesting a gradle module rename)
  • Some dependency ordering changes like with mockserver, should apply fine.
  • The liberty change looks interesting, but the need for type hierarchy change is something to watch for. And given it might require IDM approval, I think it's better to extract that.

Then a last PR that just toggles the new graph ordering.

Comment on lines +51 to 52
include(":spring-boot-shadow")
include(":smoke-test")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: I think that spring-boot-shadow, should be prefixed with smoke-test, like smoke-test-spring-boot-app

Also, maybe the existing smoke-test can be renamed to smoke-test-app (like it's custom plugin).

Comment on lines +33 to +43
// DO NOT BUMP THIS VERSION WITHOUT CHECKING THE JAR CONTENTS.
//
// The `-no-dependencies` artifacts relocate most of their dependencies under `shaded_package`,
// but leave `org.slf4j` unrelocated. That jar sorts ahead of `slf4j-api` on the test runtime
// classpath, so whichever slf4j API it embeds is the one that gets loaded:
// * 5.14.0 embeds the slf4j 1.7 API, which matches the slf4j-api version used here, and
// ships no binding of its own, so logback-classic still binds normally.
// * 5.15.0 embeds the slf4j 2.0 API plus a
// `META-INF/services/org.slf4j.spi.SLF4JServiceProvider` pointing at
// `org.slf4j.jul.JULServiceProvider`. slf4j 2.0 ignores logback 1.2's
// `org.slf4j.impl.StaticLoggerBinder`, so test logging silently reroutes to JUL.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: I don't think this is useful to stay in the code, maybe in the commit message body.

public class RequestFinishInstrumentation extends InstrumenterModule.Tracing
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {
implements Instrumenter.ForSingleType,
Instrumenter.WithTypeStructure,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

question: Are we sure about that ?

public class ResponseFinishInstrumentation extends InstrumenterModule.Tracing
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {
implements Instrumenter.ForSingleType,
Instrumenter.WithTypeStructure,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

question: Are we sure about that ?

testImplementation project(':dd-java-agent:appsec:appsec-test-fixtures')
testRuntimeOnly project(':dd-java-agent:instrumentation:osgi-4.3')
testRuntimeOnly files(filterLogbackClassic.map { it.destinationDir })
// Keep shared classloader naming and multipart hooks under test without duplicating them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nitpick: This looks like inference comment

Comment on lines +34 to +50
// `sofa-rpc-all:5.14.2` brings `netty-all:4.1.44.Final`, a fat jar carrying the classes of
// every netty module, while `grpc-netty:1.53.0` brings the individual `netty-*:4.1.79.Final`
// modules. Both supply `io.netty.**` under unrelated coordinates, so Gradle cannot reconcile
// them and whichever jar sorts first wins class loading.
//
// The `sofa-rpc-all:5.14.2` brings `netty-all:4.1.44.Final`, which is a fat jar,
// while `grpc-netty:1.53.0` brings individual `netty-*:4.1.79.Final` modules.
// Gradle 9.6.0 introduced a regression in the classpath ordering, as they are
// working on revamping the dependency traversal. The result is that the `4.1.44`
// fat jar classes appear earlier and its classes are loaded from it ; this
// produces a `NoSuchMethodError` at runtime in netty's `AbstractReferenceCountedByteBuf`.
// Forcing 4.1.79 removes the duplicate rather than reordering it: `netty-all` has shipped zero
// classes since 4.1.75, so it becomes an empty aggregator that instead pulls in the complete,
// version-aligned 4.1.79 module set - including the dns/epoll/kqueue/sctp modules that the fat
// jar was otherwise the only source of.
//
// The workaround is to force `netty-all` to `4.1.79` so the fat jar and the individual
// modules are aligned on the same version. The fix is behind Gradle 9.7's
// ENHANCED_GRAPH_ORDERING preview, which currently exposes other fat-jar conflicts.
// See https://github.com/gradle/gradle/issues/38057 for more details.
// This is deliberately independent of ENHANCED_GRAPH_ORDERING. That preview only changes where
// the fat jar lands: without it the jar sorts between `netty-buffer` and `netty-common`, so
// `AbstractReferenceCountedByteBuf` (4.1.79) is paired with `ReferenceCountUpdater` (4.1.44)
// and fails with a `NoSuchMethodError`; with it the jar sorts ahead of every modular jar, which
// hides that symptom only because all of netty then silently loads from 4.1.44. Either way the
// classpath still holds two copies of `io.netty.**`, so do not drop this constraint on the
// grounds that the preview is enabled.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nitpick: I'd humanly reword this comment. In particular if ENHANCED_GRAPH_ORDERING is enabled but that doesn't change the need of constraint (which is very fine, and expressive), then this comment can drop referring to ENHANCED_GRAPH_ORDERING.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep, I'm kind of think now that I will close this PR for now and open couple of small ones that make sense without ENHANCED_GRAPH_ORDERING.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's reopen it when your other changes land

By then maybe Gradle will probably released 9.8 or 9.9.

@AlexeyKuznetsov-DD

Copy link
Copy Markdown
Contributor Author

Closing since it is too early to switch to ENHANCED_GRAPH_ORDERING mode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: tooling Build & Tooling tag: ai generated Largely based on code generated by an AI or LLM tag: no release notes Changes to exclude from release notes type: refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants