Skip to content

Modernize Grails async integration for Spring Boot 4.1 - #16230

Draft
codeconsole wants to merge 10 commits into
apache:8.0.xfrom
codeconsole:feature/modernize-grails-async-8.0.x
Draft

Modernize Grails async integration for Spring Boot 4.1#16230
codeconsole wants to merge 10 commits into
apache:8.0.xfrom
codeconsole:feature/modernize-grails-async-8.0.x

Conversation

@codeconsole

@codeconsole codeconsole commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replace the default FutureTask-based promise implementation with a Groovy-friendly CompletableFuture implementation while retaining the existing Promise and PromiseFactory APIs.
  • Execute Grails promises on Spring Boot's managed applicationTaskExecutor, including Boot lifecycle management, graceful shutdown, pool configuration, virtual-thread support, and composed task decorators.
  • Use a Spring-managed fallback executor, with the same task decorators, in application contexts where Boot does not provide an applicationTaskExecutor.
  • Preserve executor affinity across derived CompletionStage instances, so operations such as thenApplyAsync continue using Boot's managed executor; use CompletableFuture.supplyAsync for task submission and expose the Java 21 Future state and immediate-result APIs naturally.
  • Apply Groovy 5 pattern variables and unnamed closure parameters where they make the controller and promise paths clearer without changing the public Groovy API.
  • Propagate GrailsWebRequest through a Spring TaskDecorator and remove the legacy web-promise decorator pipeline from the active execution path.
  • Use Spring's StandardServletAsyncWebRequest, DeferredResult, and WebAsyncManager for controller async lifecycle, completion, dispatch, timeout, and exception processing.
  • Preserve explicit user-supplied promise decorators and third-party PromiseFactory providers discovered through ServiceLoader.
  • Deprecate the retained FutureTask, raw Servlet async, legacy request decorator, and Spring factory-bean compatibility APIs for removal in a future release.
  • Update the async promises, async requests, and events documentation with Spring Boot configuration and migration guidance.

LOC

Area Added Removed Net Deprecated Net after deprecated removal
Production 509 92 +417 1,018 -601
Tests 429 41 +388 0 +388
Documentation 40 13 +27 0 +27
Total 978 146 +832 1,018 -186

The deprecated total includes 863 physical lines in public APIs marked @Deprecated(forRemoval = true) and 155 lines in the internal FutureTaskChildPromise, which is only required by the deprecated FutureTask implementation.

Verification

  • grails-async-core, grails-async, grails-web-url-mappings, and grails-spring-security test suites pass.
  • The previously failing async error functional tests pass in both the standard and Hibernate 7 applications on Java 21.
  • The Java 25 Micronaut ConfigLoadingSpec passes without Boot's applicationTaskExecutor.
  • Affected-module code style and dependency-version validation pass.
  • Checkstyle, CodeNarc, PMD, and SpotBugs aggregate reports from the earlier pass contain no violations.
  • The Grails user guide renders successfully.
  • The later repository-wide sweep was intentionally stopped because its aggregate clean/build behavior placed excessive memory and disk pressure on the development machine. Final verification is scoped to the affected modules.

@codeconsole

Copy link
Copy Markdown
Contributor Author

Follow-up on the legacy async-library integrations: the current PR does not remove them. The branch still builds, publishes, documents, and discovers these optional PromiseFactory implementations through ServiceLoader:

  • GPars 1.2.1 (grails-async-gpars)
  • RxJava 1.3.8 (grails-async-rxjava)
  • RxJava 2.2.21 (grails-async-rxjava2)
  • RxJava 3 (grails-async-rxjava3)

None of these libraries is required for grails-async functionality anymore. The modern baseline can be implemented entirely with JDK CompletableFuture, Spring Boot's managed applicationTaskExecutor, Spring MVC async processing, and the existing Groovy Promise facade.

Recommended disposition:

  • Remove RxJava 1 support because it is end-of-life.
  • Remove RxJava 2 support because it has been superseded by RxJava 3.
  • Remove GPars support from grails-async; it provides no required capability over the new JDK/Spring implementation.
  • Prefer removing RxJava 3 as well unless Grails intentionally wants to maintain first-party RxJava interoperability.

Removing all four adapters would leave a single supported JDK/Spring implementation while retaining the PromiseFactory SPI for independently maintained integrations. It would also eliminate the current behavior where adding a provider library to the classpath can silently replace the global promise engine through ServiceLoader.

This is a user-visible breaking change because these modules are published artifacts, so it should be documented in the Grails 8 release and migration notes. It also requires a broader repository review: grails-events-gpars remains a separate optional event bus, grails-cache exposes GPars as an API dependency, and Neo4j uses GPars in tests.

@testlens-app

This comment has been minimized.

@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.84663% with 41 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.2019%. Comparing base (c985acb) to head (b3febc2).
⚠️ Report is 16 commits behind head on 8.0.x.

Files with missing lines Patch % Lines
...tory/future/CompletableFuturePromiseFactory.groovy 72.3404% 10 Missing and 3 partials ⚠️
...ync/factory/future/CompletableFuturePromise.groovy 79.4872% 6 Missing and 2 partials ⚠️
...rc/main/groovy/grails/async/web/WebPromises.groovy 69.2308% 8 Missing ⚠️
.../web/async/mvc/AsyncActionResultTransformer.groovy 57.1429% 2 Missing and 4 partials ⚠️
...b/mapping/mvc/UrlMappingsInfoHandlerAdapter.groovy 50.0000% 2 Missing and 4 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##                8.0.x     #16230        +/-   ##
==================================================
+ Coverage     54.1599%   54.2019%   +0.0420%     
+ Complexity      20316      20301        -15     
==================================================
  Files            2107       2101         -6     
  Lines          101132     101026       -106     
  Branches        17920      17913         -7     
==================================================
- Hits            54773      54758        -15     
+ Misses          38551      38466        -85     
+ Partials         7808       7802         -6     
Files with missing lines Coverage Δ
.../grails/async/factory/PromiseFactoryBuilder.groovy 100.0000% <100.0000%> (ø)
...ctory/future/CachedThreadPoolPromiseFactory.groovy 53.3333% <ø> (-13.3333%) ⬇️
...ails/async/factory/future/FutureTaskPromise.groovy 68.2927% <ø> (-4.8781%) ⬇️
...oovy/grails/async/web/AsyncGrailsWebRequest.groovy 58.5366% <ø> (ø)
...s/web/async/AsyncWebRequestPromiseDecorator.groovy 68.2927% <ø> (-7.3171%) ⬇️
...yncWebRequestPromiseDecoratorLookupStrategy.groovy 66.6667% <ø> (-33.3333%) ⬇️
...gins/web/async/ControllersAsyncGrailsPlugin.groovy 100.0000% <100.0000%> (ø)
...grails/plugins/web/async/GrailsAsyncContext.groovy 0.0000% <ø> (ø)
...ins/web/async/GrailsWebRequestTaskDecorator.groovy 100.0000% <100.0000%> (ø)
...plugins/web/async/spring/PromiseFactoryBean.groovy 0.0000% <ø> (ø)
... and 5 more

... and 16 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant