Upgrade to Grails 7.0.0, Spring Boot 3, Groovy 4
RUN-3427: Upgrade to Grails 7.0.4, Spring Boot 3.5.8, Groovy 4.0.29
Current State (Last Updated: December 9, 2024, 11:45 PM)
Compilation: Complete with ZERO errors
Tests: 5,057 total, 4,836 passing (95.6%), 221 failing
OpenAPI: 95.74% complete (14,552/15,200 lines), zero warnings
Status: Ready for engineering review and systematic test fixing
Current Status
Build & Compilation
- rundeckapp compiles successfully
- All core plugins compile successfully
- Zero compilation errors
- Zero compilation warnings
- Full clean build succeeds
Test Results
- Total: 5,057 tests
- Passing: 4,836 (95.6%)
- Failing: 221 (4.4%)
- Categories: Property access (Groovy 4), tag library issues, complex mocking
OpenAPI Specification
- Completion: 14,552 / 15,200 lines (95.74%)
- Endpoints: 250 (all present)
- Warnings: 0 (all eliminated)
- Examples: 85 response examples transformed for Micronaut OpenAPI 6.x
Major Version Updates
| Component | From | To |
|---|---|---|
| Grails | 6.1.2 | 7.0.4 |
| Spring Boot | 2.7.18 | 3.5.8 |
| Spring | 5.3.39 | 6.2.14 |
| Groovy | 3.0.25 | 4.0.29 |
| Gradle | 7.6.2 | 8.14.3 |
| Jetty | 11.x | 12.0.17 (EE10) |
| Micronaut | 3.10.4 | 4.9.2 |
Major Technical Changes
1. Complete JAAS Refactoring
Issue: Jetty 12 removed all org.eclipse.jetty.jaas.* packages
Solution: Complete refactoring to standard Java JAAS APIs
Custom Classes Created (in org.rundeck.jaas):
-
UserInfo.java- Replaces Jetty UserInfo -
PasswordCredential.java- Replaces Jetty Credential -
ObjectCallback.java- Replaces Jetty ObjectCallback -
RundeckPrincipal.java- Replaces JAASPrincipal -
RundeckRole.java- Replaces JAASRole -
AbstractLoginModule.java- Base class with common logic -
PropertyFileLoginModule.java- Property file authentication
Modules Refactored (8 authentication modules):
-
JNDILoginModule.java -
JettyCachingLdapLoginModule.java -
JettyCombinedLdapLoginModule.java -
ReloadablePropertyFileLoginModule.java -
JettyRolePropertyFileLoginModule.java -
JettyAuthPropertyFileLoginModule.java -
JettySupport.java
Result: Zero Jetty dependencies, standard Java JAAS throughout
Feature Added: Case-insensitive username normalization via rundeck.security.auth.caseInsensitiveUsername
2. Jakarta EE Migration (javax → jakarta)
Scope: All packages migrated
-
javax.servlet.*→jakarta.servlet.* -
javax.validation.*→jakarta.validation.* -
javax.xml.bind.*→jakarta.xml.bind.* -
javax.cache.*→jakarta.cache.* - Configuration properties updated
- Build dependencies migrated
Files Affected: 7,500+ files
Approach: OpenRewrite for automation + manual verification for constants
3. Micronaut OpenAPI 6.x Migration
Version Jump: 4.8.7 → 6.16.0 introduced breaking annotation pattern changes
Key Changes Required:
-
@ApiResponsemoved outside@Operation(was nested before) - Added
produces = MediaType.APPLICATION_JSONto HTTP verb annotations - MediaType constants instead of strings
- Response examples require array of named
@ExampleObjectinstances
Controllers Updated:
- 24 endpoints transformed across 11 controllers
- 62 response examples fixed
- 7 missing endpoints added (2 project export, 5 metrics)
- 20 missing response codes added
Pattern Documented: .github/copilot-instructions.md updated with Micronaut 6.x patterns
4. Groovy 4 Compatibility
Syntax Changes Applied:
- Ternary operator:
.?before?:no longer works, split into separate expressions - Boolean properties:
isEnabledno longer generatesgetEnabled(), useisEnabled()directly - Type inference: Stricter, requires explicit casts in some contexts
- XML parser imports:
groovy.util.XmlParser→groovy.xml.XmlParser - Direct field access: Use
this.@idto avoid recursion in custom getters
Build Files Updated:
- Forced Groovy 4 for all transitive dependencies
- Resolution strategy configured in all build.gradle files
5. Asset Pipeline Migration
Migration: org.grails.plugins → cloud.wondrify coordinates
Changes:
// Old
plugins {
id "com.bertramlabs.asset-pipeline" version "3.4.7"
}
dependencies {
implementation "org.grails.plugins:asset-pipeline-grails:3.4.7"
}
// New
plugins {
id "cloud.wondrify.asset-pipeline" version "5.0.12"
}
dependencies {
implementation "cloud.wondrify:asset-pipeline-grails:5.0.12"
}
Files Updated: 15+ plugins
6. Java 17 Module System for Testing
Issue: NoClassDefFoundError for CGLIB mocking in tests
Solution: Add --add-opens JVM arguments
test {
useJUnitPlatform()
jvmArgs += [
'--add-opens=java.base/java.lang=ALL-UNNAMED',
'--add-opens=java.base/java.lang.reflect=ALL-UNNAMED',
'--add-opens=java.base/java.util=ALL-UNNAMED'
]
}
Applied To: rundeckapp + core plugins
7. Jakarta Validation Provider
Issue: jakarta.validation.NoProviderFoundException during tests
Solution: Add validation implementation dependencies
testImplementation "org.hibernate.validator:hibernate-validator:8.0.1.Final"
testImplementation "org.glassfish.expressly:expressly:5.0.0"
Applied To: rundeckapp build.gradle
Test Fixing Patterns
Pattern #1: @Integration Conflicts
Issue: @Integration incompatible with unit test traits in Grails 7
Symptom: IllegalStateException during test initialization
Solution: Remove @Integration annotation from unit tests
Applied: AsyncImportServiceSpec (17 fixes)
Pattern #2: Wrong Test Traits
Issue: Service specs using ControllerUnitTest instead of ServiceUnitTest
Symptom: MissingFieldException, missing methods, mocked domains broken
Solution:
- Change to correct trait (
ServiceUnitTestfor services) - Remove controller-only methods (e.g.,
mockCodec()) - Ensure mocked domains have explicit IDs in Mock closures
Example:
// ❌ WRONG
class ApiServiceSpec implements ControllerUnitTest<ApiController>, DataTest {
void setup() {
mockCodec(JSONCodec) // Doesn't exist!
provider.userDataProvider = Mock(...){
findUser(_) >> new User(login: 'auser').save() // id not set!
}
}
}
// ✅ CORRECT
class ApiServiceSpec implements ServiceUnitTest<ApiService>, DataTest {
void setup() {
// No mockCodec
provider.userDataProvider = Mock(...){
findUser(_) >> {
def user = new User(login: 'auser')
user.id = 1L // Explicit!
user.save()
return user
}
}
}
}
Applied: ApiServiceSpec (17 fixes)
Dependency Changes
Added
-
org.apache.grails:grails-micronaut- Grails 7 Micronaut integration -
org.apache.grails:grails-layout- Sitemesh 2.6.x for Grails 7 -
org.hibernate.validator:hibernate-validator:8.0.1.Final- Jakarta Validation -
org.glassfish.expressly:expressly:5.0.0- Expression Language - Java 17
--add-opensJVM arguments for CGLIB/Spock
Removed
- All Jetty JAAS dependencies (
org.eclipse.jetty.jaas.*) -
grails-test-mixins(obsolete in Grails 7) - Legacy Hibernate cache configuration (SingletonEhCacheRegionFactory)
Updated
- Asset Pipeline →
cloud.wondrify:asset-pipeline-grails:5.0.12 - All Grails dependencies →
org.apache.grailsnamespace - All Spring dependencies → Spring 6.2.14
- Hibernate cache → Jakarta JCache (Ehcache 3)
Breaking Changes
API Changes
- None expected - all public APIs preserved
Configuration Properties
-
hibernate.cache.region.factory_class = "jcache"(Jakarta cache properties) - New feature flag:
rundeck.security.auth.caseInsensitiveUsername(optional)
Runtime Requirements
- Java 17+ required (was Java 11)
- Spring Boot 3.5.8 (Jakarta EE namespace)
- Groovy 4.0.29 (syntax changes in some contexts)
Related PRs
Pro PR: rundeckpro/rundeckpro #4515 (grails7-upgrade branch)
- Uses this Core commit via submodule
- Must be reviewed/merged in parallel
Migration Notes
See GRAILS7_MIGRATION_NOTES.md in Pro repository for:
- Detailed upgrade patterns
- Test fixing patterns discovered
- OpenAPI migration details
- Quick reference for common issues
Next Steps
- Engineering Review: Review upgrade approach and remaining test failures
- Test Fixing: Systematic fixes for remaining 221 failures
- Integration Testing: Run full integration test suite
- Functional Testing: Run functional tests
- QA Cycle: Complete QA testing
- Performance Testing: Validate no regressions
Testing Notes
How to Build
export JAVA_HOME="/path/to/java17"
./gradlew clean build
How to Run Tests
./gradlew :rundeckapp:test --continue
Known Test Patterns
- Some specs need Jakarta Validation provider (added to rundeckapp)
- Java 17 module access configured for CGLIB/Spock
- Wrong test trait usage causes MissingFieldException
- See GRAILS7_MIGRATION_NOTES.md for detailed patterns
Related Ticket: RUN-3427
Branch: grails7-upgrade
Target: main