feat: Replace Spring Dependency Management plugin with Gradle platform + lightweight BOM property overrides#15467
feat: Replace Spring Dependency Management plugin with Gradle platform + lightweight BOM property overrides#15467jamesfredley wants to merge 20 commits into8.0.xfrom
Conversation
…m and lightweight BOM property overrides Replace the Spring Dependency Management Gradle plugin with Gradle's native platform() support plus a lightweight BomManagedVersions utility that preserves the ability to override BOM-managed dependency versions via project properties (ext[] or gradle.properties). This allows Grails to standardize on Gradle platforms - the modern dependency management solution - while retaining the one feature Gradle platforms lack: property-based version overrides from BOMs. Changes: - Add BomManagedVersions: parses BOM POM XML to extract property-to- artifact mappings, applies version overrides via eachDependency() - Update GrailsGradlePlugin to use platform() + BomManagedVersions instead of Spring DM plugin - Deprecate GrailsExtension.springDependencyManagement flag - Remove Spring DM plugin from plugins/build.gradle dependency - Remove Spring DM plugin from example projects - Update documentation to reflect Gradle platform approach - Add unit tests (BomManagedVersionsSpec) and functional test (BomPlatformFunctionalSpec) Note: build-logic/docs-core/ExtractDependenciesTask still uses Spring DM's shaded Maven model classes and should be addressed in a follow-up. Assisted-by: Claude Code <Claude@Claude.ai>
There was a problem hiding this comment.
Pull request overview
This PR replaces the Spring Dependency Management plugin with Gradle's native platform() mechanism and introduces a lightweight utility (BomManagedVersions) to enable property-based version overrides from BOMs—the one feature Gradle platforms don't natively support.
Changes:
- Removed Spring Dependency Management plugin dependency and usage across the codebase
- Added
BomManagedVersionsutility (~350 lines) to parse BOM POMs and apply property-based version overrides - Updated plugin to use
platform()for BOM import instead of Spring DM'smavenBom()
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/BomManagedVersions.groovy |
New utility class that parses BOM POMs and enables property-based version overrides via Gradle's resolution strategy |
grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy |
Replaced Spring DM plugin application with native Gradle platform support plus BomManagedVersions |
grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsExtension.groovy |
Deprecated springDependencyManagement flag with guidance on new approach |
grails-gradle/plugins/build.gradle |
Removed Spring DM plugin dependency |
grails-test-examples/gsp-spring-boot/app/build.gradle |
Removed Spring DM plugin from example project |
grails-data-graphql/examples/spring-boot-app/build.gradle |
Removed Spring DM plugin from example project |
grails-doc/src/en/guide/commandLine/gradleBuild/gradleDependencies.adoc |
Updated documentation to reflect platform-based approach and property override mechanism |
grails-profiles/plugin/templates/grailsCentralPublishing.gradle |
Updated comment to reflect new version management approach |
grails-bom/build.gradle |
Updated comment about version property references |
grails-gradle/plugins/src/test/groovy/org/grails/gradle/plugin/core/BomManagedVersionsSpec.groovy |
Unit tests for BOM parsing and property extraction |
grails-gradle/plugins/src/test/groovy/org/grails/gradle/plugin/core/BomPlatformFunctionalSpec.groovy |
Functional test verifying platform integration |
grails-gradle/plugins/src/test/resources/test-projects/bom-platform-basic/* |
Test project files for functional testing |
grails-gradle/plugins/src/test/resources/test-poms/test-bom.pom |
Test BOM POM for unit tests |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…h constant Add factory.setXIncludeAware(false) for explicit XML security hardening and extract magic number 10 to MAX_PROPERTY_INTERPOLATION_DEPTH constant. Assisted-by: Claude Code <Claude@Claude.ai>
eb85805 to
5643538
Compare
The Spring Dependency Management plugin applied version constraints globally to every configuration via configurations.all() and resolutionStrategy.eachDependency(). With the switch to Gradle's native platform(), version constraints must be added explicitly. Apply the grails-bom platform to all declarable configurations using configureEach, matching the previous global behavior. Non-declarable configurations (apiElements, runtimeElements, etc.) inherit constraints through their parent configurations. Code quality tool configurations (checkstyle, codenarc, etc.) are excluded because platform() constraints participate in version conflict resolution and can upgrade transitive dependencies, breaking the tools. Also ensure the developmentOnly configuration always exists via maybeCreate. Assisted-by: Claude Code <Claude@Claude.ai>
5643538 to
5e89656
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
# Conflicts: # grails-data-graphql/examples/spring-boot-app/build.gradle
…atform applyGrailsBom() adds a regular platform(grails-bom) to every declarable configuration, including 'implementation'. Micronaut projects still need to declare an enforcedPlatform(grails-bom) explicitly. With both present on the same configuration, validateEnforcedBom() was failing on the first grails-bom it encountered (the plugin-injected regular platform) before it could check the user's enforcedPlatform declaration. Scan all grails-bom declarations on 'implementation' and accept the project as correctly configured if any one of them is an enforcedPlatform. Only error when grails-bom is present but no enforcedPlatform exists. Assisted-by: claude-code:claude-opus-4
…form applyGrailsBom() adds a regular platform(grails-bom) to every declarable configuration. For annotation-processor classpaths this conflicts with the platform the user imports themselves (typically io.micronaut.platform:micronaut-platform for Micronaut projects). Since platform() constraints participate in Gradle version conflict resolution, grails-bom's higher javaparser-core version wins over what Micronaut's inject-java processor was compiled against, producing NoSuchMethodError on StaticJavaParser.parseJavadoc(String) during compileJava. Exclude annotationProcessor and *AnnotationProcessor configurations using the same mechanism that already excludes code-quality tool classpaths (checkstyle, codenarc, pmd, spotbugs). Rename the helper to reflect the broadened scope. Assisted-by: claude-code:claude-opus-4
| Note that version numbers are not present in the majority of the dependencies. | ||
|
|
||
| This is thanks to the Spring dependency management plugin which automatically configures `grails-bom` as a Maven BOM via the Grails Gradle Plugin. This defines the default dependency versions for most commonly used dependencies and plugins. | ||
| This is thanks to Gradle's platform support which automatically imports `grails-bom` as a managed dependency platform via the Grails Gradle Plugin. This defines the default dependency versions for most commonly used dependencies and plugins. |
There was a problem hiding this comment.
The versions are only supported by your plugin. Also, the dependency management plugin causes the bom versions to resolve to the spring version and not to the highest version. We should discuss this side effect in the weekly
There was a problem hiding this comment.
Updated in 2ca302b. The relevant section in grails-doc/src/en/guide/commandLine/gradleBuild/gradleDependencies.adoc now:
-
Explicitly calls out that the property-override mechanism is a feature of the new
org.apache.grails.gradle.bom-property-overridesplugin and links to Support overriding BOM version properties with ext gradle/gradle#9160 so readers know it isn't native Gradle behaviour. -
Adds a NOTE block describing the resolution side effect you raised: the plugin participates in Gradle's standard conflict resolution, so when an artifact is requested at multiple versions Gradle's highest-version-wins picks the version, and the property-override pins what wins after that. This contrasts with the legacy Spring DM plugin which forced BOM versions unconditionally and could mask transitive drift.
enforcedPlatform(grails-bom)is documented as the escape hatch for stricter behaviour. -
Also documents how the new plugin can be applied standalone (non-Grails projects) so the property-override workflow isn't tied to
grails-bom.
Happy to refine the side-effect wording further after the weekly discussion - leaving this thread open in case you want to expand on it.
| * @since 8.0 | ||
| */ | ||
| protected void applyGrailsBom(Project project) { | ||
| String grailsVersion = (project.findProperty('grailsVersion') ?: BuildSettings.grailsVersion) as String |
There was a problem hiding this comment.
We need to extract the property features of this PR into it's own plugin separate from the bom so that it can be generically used / maintained.
There was a problem hiding this comment.
Done in 2ca302b - extracted into a new standalone subproject grails-gradle/bom-property-overrides (published as grails-gradle-bom-property-overrides). The plugin id org.apache.grails.gradle.bom-property-overrides is BOM-agnostic and can be applied directly to any project that consumes a Maven-style BOM, no Grails dependency required.
Highlights:
BomManagedVersionsmoved toorg.grails.gradle.plugin.bomand now accepts aCollection<String>of BOM coordinates- New
BomPropertyOverridesPluginexposes abomPropertyOverridesextension with both modes you asked for:autoDetect(defaulttrue, scans every declaredplatform()/enforcedPlatform()) and an explicitbom 'group:artifact:version'list GrailsGradlePlugin.applyGrailsBomnow just applies the new plugin instead of inliningBomManagedVersions.resolve(...); auto-detect handles both the plugin-injectedplatform(grails-bom)and any user-declaredenforcedPlatform(grails-micronaut-bom)automatically- Tests: 4 unit specs for
BomManagedVersions, 7ProjectBuilderspecs for the plugin, 1 TestKit functional spec for standalone application, plusBomPlatformFunctionalSpecupdated to verify the plugin is applied throughgrails-app(13 + 12 = 25 tests, all passing) - Standalone usage is documented in
grails-doc/.../gradleDependencies.adoc
Resolves conflict in GrailsGradlePlugin.validateMicronautBom: 8.0.x split the BOM into grails-base-bom, grails-bom (default), grails-hibernate5-bom and grails-micronaut-bom. Micronaut projects now declare enforcedPlatform(grails-micronaut-bom) - a different artifact than the plugin-injected platform(grails-bom) - so the previous grailsBomPresent tracking that was needed when both were the same artifact is no longer required. Adopt 8.0.x's logic that scans for grails-micronaut-bom as enforcedPlatform and update the surrounding comment to reflect the split-BOM model. Assisted-by: claude-code:claude-opus-4-7
…5467 review) Addresses jdaugherty's review feedback on PR #15467: 1. The property-override mechanism (formerly inlined in GrailsGradlePlugin.applyGrailsBom) is now published as a standalone, BOM-agnostic plugin: org.apache.grails.gradle.bom-property-overrides (new grails-gradle-bom-property-overrides module). The plugin: - Lives in its own subproject so it can be maintained / consumed independently of the Grails BOM - Auto-detects every platform()/enforcedPlatform() declaration on the project's configurations and registers each unique BOM for property-override processing - Exposes a 'bomPropertyOverrides' extension with autoDetect (default: true) and an explicit boms list for advanced cases - Re-uses the BomManagedVersions utility (moved to org.grails.gradle.plugin.bom) which now accepts a Collection of BOM coordinates GrailsGradlePlugin.applyGrailsBom now applies the new plugin in place of the inline BomManagedVersions usage; the auto-detect logic picks up the platform(grails-bom) we declare plus any additional platforms (e.g. enforcedPlatform(grails-micronaut-bom)) the user adds. 2. Documentation (grails-doc/.../gradleDependencies.adoc) is updated to: - Clarify that property overrides are a feature of the bom-property-overrides plugin (not native Gradle), with a link to gradle/gradle#9160 - Note Gradle's highest-version-wins conflict resolution and the difference vs the legacy Spring Dependency Management plugin (which forced BOM versions unconditionally) - Document the new plugin's standalone usage for non-Grails projects with the bomPropertyOverrides extension API Tests: - BomManagedVersionsSpec moved to the new module (4 tests, all passing) - New BomPropertyOverridesPluginSpec (7 ProjectBuilder tests covering plugin application, extension defaults, DSL methods, and detectDeclaredBoms) - New BomPropertyOverridesPluginFunctionalSpec (TestKit end-to-end test verifying standalone plugin application) - BomPlatformFunctionalSpec updated to also verify the bom-property-overrides plugin is applied All 13 plugin tests + 12 bom-property-overrides tests pass; codeStyle clean on both modules. Assisted-by: claude-code:claude-opus-4-7
After the 8.0.x merge, grails-app projects now auto-inject platform(grails-bom) on every declarable configuration, while Micronaut test projects additionally declare enforcedPlatform(grails-micronaut-bom). The previous detectBomPath() returned the first BOM dependency it encountered, which after iteration order changes ends up being grails-bom, causing the validator to compare resolved versions (Groovy 5.0.5 / Spock 2.4-groovy-5.0 from Micronaut's enforced platform) against grails-bom's expected versions (Groovy 4.0.31 / Spock 2.3-groovy-4.0). detectBomPath now prefers an enforcedPlatform declaration over a regular platform: it returns the first enforcedPlatform BOM it finds, falling back to the first regular platform BOM only if no enforced declaration exists. The enforced BOM is the one whose constraints actually win at resolution time, so it is the correct reference for the validator's expected versions. Fixes the failing :grails-test-examples-issue-11767:validateDependencyVersions and :grails-test-examples-micronaut-groovy-only:validateDependencyVersions tasks on PR #15467 CI. Assisted-by: claude-code:claude-opus-4-7
CI status update (commit 2ca302b + bbf5e53)After the latest commits, all 31 in-scope CI jobs pass. Key fixes applied to the failing checks: Fixed in bbf5e53 -
|
| Status | Count | Notes |
|---|---|---|
| ✅ pass | 31 | including my fix for the validator |
| ⏭️ skip | 5 | release-only jobs |
| ❌ fail | 1 | build_grails - pre-existing 8.0.x issue, not caused by this PR |
🚨 TestLens detected 1 failed tests 🚨Here is what you can do:
Test Summary
🏷️ Commit: 0178a10 Test FailureFormTagLib2Tests > testDatePickerTagWithMinutePrecision() (:grails-gsp:test in CI / Build Grails-Core (windows-latest, 25))
Muted TestsSelect tests to mute in this pull request:
Reuse successful test results:
Click the checkbox to trigger a rerun:
Learn more about TestLens at testlens.app. |
|
Triage of the two red checks on commit 1.
|
Summary
This PR standardizes Grails on Gradle's native
platform()dependency management - the modern, recommended approach - while preserving the one feature that Gradle platforms currently lack: property-based version overrides from BOMs (e.g.ext['slf4j.version'] = '2.0.9'orslf4j.version=2.0.9ingradle.properties).The Spring Dependency Management plugin served Grails well, but it duplicates significant Gradle functionality (dependency resolution, exclusions, BOM imports) and introduces behavioral differences from standard Gradle resolution. Gradle platforms now handle the vast majority of what the Spring DM plugin provided. The one gap is reading
<properties>from BOM POMs and allowing project-level overrides (Gradle issue #9160 - confirmed "very unlikely" to be implemented).This PR bridges that gap with a lightweight ~350-line utility (
BomManagedVersions) so Grails can remove the Spring DM plugin entirely.What This Changes
Core:
BomManagedVersions.groovy(new)A lightweight utility that:
@pomartifact notation)DocumentBuilderFactory- no external dependencies. XML parsing is hardened (disallow-doctype-decl, external entity/DTD access disabled,setXIncludeAware(false))<properties>block and<dependencyManagement>entries, building a mapping of property names to the artifacts they control<scope>import</scope>BOM imports (e.g., grails-bom imports spring-boot-dependencies). Interpolation is capped atMAX_PROPERTY_INTERPOLATION_DEPTH = 10to prevent runaway recursionConfiguration.resolutionStrategy.eachDependency()- only for properties whereproject.hasProperty(name)is true (covering bothext['...']andgradle.properties)Plugin:
GrailsGradlePlugin.groovy(modified)applySpringDependencyManagementPlugin()withapplyGrailsBom()applyGrailsBom()addsproject.dependencies.platform(bomCoordinates)to every declarable project configuration (mirroring the global behaviour that Spring DM provided viaconfigurations.all() + resolutionStrategy.eachDependency()). Non-declarable configurations (apiElements,runtimeElements, etc.) inherit the constraints through their parent configurations.platform()constraints participate in version conflict resolution and would otherwise upgrade transitive dependencies and break the tool:checkstyle,codenarc,pmd,spotbugs,spotbugsPluginsannotationProcessorand any*AnnotationProcessor(e.g.testAnnotationProcessor,integrationTestAnnotationProcessor). Micronaut projects importio.micronaut.platform:micronaut-platformon these configurations themselves; a second non-enforced grails-bom platform would let Gradle's highest-version conflict resolution upgradejavaparser-core,micronaut-inject-java, etc. beyond what the processor was compiled against.developmentOnlyconfiguration is always available viaconfigurations.maybeCreate('developmentOnly'), even when Spring Boot isn't applied or plugin ordering changes.BomManagedVersions.resolve()in anafterEvaluateblock to enable property-based overrides.validateEnforcedBom()(used byconfigureMicronaut()) now scans allgrails-bomdeclarations on theimplementationconfiguration and accepts the project as correctly configured if any one of them is anenforcedPlatform. This is required becauseapplyGrailsBom()injects a regularplatform(grails-bom)alongside the user's explicitenforcedPlatform(grails-bom); the previous first-match logic tripped on the plugin-injected one and rejected valid Micronaut projects.Extension:
GrailsExtension.groovy(modified)springDependencyManagementflag is deprecated with a message directing users to Gradle's native platform supportBuild:
plugins/build.gradle(modified)implementation 'io.spring.gradle:dependency-management-plugin'dependencyExamples & Docs
io.spring.dependency-managementplugin from example projects (gsp-spring-boot,graphql spring-boot-app)gradleDependencies.adocto document the Gradle platform approach and property override mechanismgrailsCentralPublishing.gradletemplate andgrails-bom/build.gradleHow Version Overrides Work
The mechanism is intentionally simple and mirrors exactly what the Spring DM plugin provided:
BomManagedVersionsreads the BOM's<properties>to discover thatslf4j.versioncontrolsorg.slf4j:slf4j-api(and other slf4j artifacts), then useseachDependencyto force the override at resolution time.Why Not Just Use Gradle Platforms?
Gradle platforms (
platform()) handle:Gradle platforms do not handle reading
<properties>from BOM POMs and allowing project-level overrides (Gradle issue #9160 - confirmed "very unlikely" to be implemented).This is the specific gap
BomManagedVersionsfills.Testing
BomManagedVersionsSpec): 4 tests covering POM parsing, property extraction, nested BOM imports, and property-to-artifact mappingBomPlatformFunctionalSpec): End-to-end Gradle TestKit test verifying thatgradle.propertiesoverrides are applied during dependency resolution8.0.x: Core/Forge/Gradle Plugin builds, all Functional / Hibernate5 / Mongodb functional test matrices (Java 21 & 25, indy=false & indy=true), Analyze (java), Validate Dependency Versions, rat-audit, Code Style, CodeQLCommits
ca495abfeat: replace Spring Dependency Management plugin with Gradle platform + lightweight BOM property overrides- initial implementation ofBomManagedVersions, switch from Spring DM toplatform(), deprecateGrailsExtension.springDependencyManagement, update docs and example projects531041baAddress review: add XInclude hardening and extract interpolation depth constant-setXIncludeAware(false)and extractMAX_PROPERTY_INTERPOLATION_DEPTH = 105e89656efix: apply grails-bom platform to all declarable configurations- restore Spring DM's global behaviour by addingplatform(grails-bom)to every declarable configuration; exclude code-quality tool classpaths1a2cea34→28a68177Merge branch '8.0.x' into feat/gradle-managed-version-overridescommits keeping the branch in sync with8.0.x(Spring Boot 4, Jackson 3, Java 25 Micronaut bumps, dependency-validator plugin, etc.). The final merge resolved one textual conflict ingrails-data-graphql/examples/spring-boot-app/build.gradle(drop the Spring DM plugin while keepingorg.apache.grails.buildsrc.dependency-validator, matching how other examplebuild.gradles on8.0.xlook).77932f2efix: allow Micronaut BOM validator to coexist with plugin-injected platform-validateEnforcedBom()now scans allgrails-bomdeclarations and passes if any one isenforcedPlatform, rather than failing on the first regularplatform(grails-bom)the plugin injected54fbe932fix: exclude annotation-processor configurations from grails-bom platform- do not addplatform(grails-bom)toannotationProcessor/*AnnotationProcessorconfigurations; renameisCodeQualityConfigurationtoisExcludedFromBomPlatform. FixesNoSuchMethodErroronStaticJavaParser.parseJavadoc(String)in Micronaut test-example projects after the8.0.xbump to javaparser-core 3.28.0Known Follow-up Items
build-logic/docs-core/ExtractDependenciesTask.groovystill uses Spring DM's shaded Maven model classes (io.spring.gradle.dependencymanagement.org.apache.maven.model.Model) for POM reading at build time. This should be migrated to JDK XML parsing in a separate follow-up.Motivation
Standardizing on Gradle platforms is the direction the Gradle ecosystem is heading. The Spring DM plugin, while feature-rich, introduces a parallel dependency resolution system that can conflict with Gradle's native behavior. By removing it and adding only the minimal property-override bridge, Grails 8 gets:
ext['version.property']andgradle.propertiesoverrides continue to work exactly as beforeRelated