close
Skip to content

Restore micronaut-jackson-databind for grails-forge-web-netty JSON runtime#15651

Merged
jamesfredley merged 1 commit into
apache:8.0.xfrom
jamesfredley:ci/forge-web-netty-jackson-databind
May 8, 2026
Merged

Restore micronaut-jackson-databind for grails-forge-web-netty JSON runtime#15651
jamesfredley merged 1 commit into
apache:8.0.xfrom
jamesfredley:ci/forge-web-netty-jackson-databind

Conversation

@jamesfredley

Copy link
Copy Markdown
Contributor

Summary

PRs #15646 / #15647 / #15648 fixed the broken dockerBuildNative and Cloud Run startup paths so the Forge deploy workflows (prev-snapshot run 25579824416, next run 25579825356) finished green and the next.grails.org (8.0.0-M1) and prev-snapshot.grails.org (8.0-SNAPSHOT) Cloud Run revisions went live.

start.grails.org still does not list 8.0.0-M1 / 8.0-SNAPSHOT in the Grails Version selector, though, because the freshly deployed backends both return HTTP 500 on every JSON endpoint:

$ curl -i -H 'Origin: https://start.grails.org' https://next.grails.org/versions
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
...
{"_embedded":{"errors":[{"message":"Internal Server Error: Error encoding object
 [org.grails.forge.api.VersionDTO@4a8eebd4] to JSON: No serializable introspection
 present for type VersionDTO. Consider adding Serdeable.Serializable annotate to
 type VersionDTO. Alternatively if you are not in control of the project's source
 code, you can use @SerdeImport(VersionDTO.class) to enable serialization of this
 type."}]},"message":"Internal Server Error"}

The frontend's grails-version-feed.json explicitly fans out to latest.grails.org, snapshot.grails.org, next.grails.org, prev.grails.org, and prev-snapshot.grails.org. The first three are 7.1.x release builds and return 200; the latter two are our new 8.0.x builds and return 500, so the version selector skips them and the only Grails versions visible at start.grails.org today are 7.1.1, 7.1.2-SNAPSHOT, 7.0.11. The browser additionally reports CORS errors on those 500 responses because Micronaut's error path bypasses the CORS filter (separate hardening, out of scope for this PR).

Root cause

PR #15365 review feedback (commit e1f3013070) swapped the runtime JSON dependency in grails-forge-web-netty/build.gradle:

-    runtimeOnly 'io.micronaut:micronaut-jackson-databind'
+    runtimeOnly 'io.micronaut.serde:micronaut-serde-jackson'

with the rationale that "the forge stack is a Micronaut 4 serde-jackson application; the legacy jackson-databind runtime was a copy-paste from an earlier generator template." That is not yet true: every DTO returned by the Forge controllers (VersionDTO, ApplicationTypeDTO, FeatureDTO, FeatureList, SelectOptionsDTO, GormImplDTO, JdkVersionDTO, LanguageDTO, ServletImplDTO, DevelopmentReloadingDTO, GitHubCreateDTO, PreviewDTO, ApplicationTypeList, LinkDTO, the abstract Linkable parent, ...) carries @Introspected only - none of them are @Serdeable or registered via @SerdeImport. Micronaut Serde correctly rejects @Introspected-only classes at runtime; Micronaut Jackson Databind serialises them via Jackson + bean introspection. The mismatch turns every JSON endpoint behind next.grails.org and prev-snapshot.grails.org into a 500.

The other Forge modules - forge-api tests, forge-core, analytics-postgres - all still use micronaut-jackson-databind; web-netty was the only inconsistency.

Fix

Restore runtimeOnly 'io.micronaut:micronaut-jackson-databind' in grails-forge-web-netty/build.gradle so the JSON runtime matches the rest of the forge stack and the existing @Introspected DTOs serialise. A proper end-to-end Serde migration belongs in a separate PR that also annotates every Forge DTO with @Serdeable / @SerdeImport at the same time.

Verification

Reproduced locally on Docker 29.3.0 with the same Java 21 GraalVM Community 21.0.2-ol9 native-image container the deploy workflows use, then rebuilt with the fix:

$ docker run -d --name web-netty-local -p 18080:8080 \
    -e MICRONAUT_SERVER_PORT=8080 \
    -e CORS_ALLOWED_ORIGIN=https://start.grails.org \
    grails-forge-local:web-netty

$ curl -i -H 'Origin: https://start.grails.org' http://localhost:18080/versions
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://start.grails.org
Vary: Origin
Access-Control-Allow-Credentials: true
content-type: application/json
...
{"_links":{"self":{"href":"http://.../versions","templated":false}},
 "versions":{...,"grails.version":"8.0.0-M1",...,"groovy.version":"4.0.31",
             "spring-boot.version":"4.0.5",...}}

$ curl -i -H 'Origin: https://start.grails.org' http://localhost:18080/select-options
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://start.grails.org
...

Endpoints serve JSON cleanly with the CORS allow-origin header. Once next.grails.org and prev-snapshot.grails.org are redeployed from this commit, the start.grails.org Grails Version selector will pick up 8.0.0-M1 and 8.0-SNAPSHOT.

…ntime

After PR apache#15647 + apache#15648 fixed dockerBuildNative + Cloud Run startup
for the analytics service, both Forge deploy workflows finally
finished green and pushed `next.grails.org` (8.0.0-M1 hotfix branch)
and `prev-snapshot.grails.org` (8.0.x) revisions live. start.grails.org
still does not list 8.0.0-M1 / 8.0-SNAPSHOT in the Grails Version
selector, though, because both backends return:

  $ curl -i -H 'Origin: https://start.grails.org' https://next.grails.org/versions
  HTTP/1.1 500 Internal Server Error
  Content-Type: application/json
  ...
  {"_embedded":{"errors":[{"message":"Internal Server Error: Error
   encoding object [org.grails.forge.api.VersionDTO@..] to JSON: No
   serializable introspection present for type VersionDTO. Consider
   adding Serdeable.Serializable annotate to type VersionDTO."}]},
   "message":"Internal Server Error"}

Reproduced locally (`docker run grails-forge-local:web-netty` against
the GraalVM Community 21.0.2-ol9 native image with CORS_ALLOWED_ORIGIN
set, then `curl -H 'Origin: https://start.grails.org' /versions`) -
identical 500.

Root cause: PR apache#15365 review feedback (commit e1f3013,
"Address PR review feedback: telemetry override, forge BOM
adoption, publish plugin bump") swapped grails-forge-web-netty's
runtime JSON dependency:

  -    runtimeOnly 'io.micronaut:micronaut-jackson-databind'
  +    runtimeOnly 'io.micronaut.serde:micronaut-serde-jackson'

with the rationale that "the forge stack is a Micronaut 4 serde-
jackson application; the legacy jackson-databind runtime was a
copy-paste from an earlier generator template". That is not yet
true: every DTO returned by the Forge controllers
(VersionDTO, ApplicationTypeDTO, FeatureDTO, FeatureList,
SelectOptionsDTO, GormImplDTO, JdkVersionDTO, LanguageDTO,
ServletImplDTO, DevelopmentReloadingDTO, GitHubCreateDTO,
PreviewDTO, ApplicationTypeList, LinkDTO, the abstract Linkable
parent, ...) carries `@Introspected` only - none of them are
`@Serdeable` or registered via `@SerdeImport`. Micronaut Serde
correctly rejects @Introspected-only classes at runtime, while
Micronaut Jackson Databind serialises them via Jackson + bean
introspection. The mismatch turns every JSON endpoint behind
next.grails.org and prev-snapshot.grails.org into a 500, which
is why Forge UI's `versions` fetch is empty for those backends
and 8.0.0-M1 is missing from the start.grails.org Grails Version
selector.

The other Forge modules (forge-api tests, forge-core, analytics-
postgres) all still use micronaut-jackson-databind; web-netty
was the only inconsistency. Restore web-netty to
micronaut-jackson-databind so the runtime matches the rest of the
forge stack and the existing @introspected DTOs serialise. A
proper end-to-end Serde migration belongs in a separate PR that
also annotates every Forge DTO with @Serdeable / @SerdeImport.

Verified locally on Docker 29.3.0 with the same Java 21 GraalVM
Community 21.0.2-ol9 native-image container the deploy workflows
use:

  $ curl -i -H 'Origin: https://start.grails.org' http://localhost:18080/versions
  HTTP/1.1 200 OK
  Access-Control-Allow-Origin: https://start.grails.org
  Vary: Origin
  Access-Control-Allow-Credentials: true
  content-type: application/json
  ...
  {"_links":{"self":{"href":"http://.../versions","templated":false}},
   "versions":{...,"grails.version":"8.0.0-M1",...}}

  $ curl -i -H 'Origin: https://start.grails.org' http://localhost:18080/select-options
  HTTP/1.1 200 OK
  Access-Control-Allow-Origin: https://start.grails.org
  ...

Endpoints serve JSON cleanly; CORS allow-origin header is present;
the start.grails.org Grails Version selector will pick up 8.0.0-M1
once next.grails.org is redeployed from this commit.

Assisted-by: claude-code:claude-4.7-opus
Copilot AI review requested due to automatic review settings May 8, 2026 22:01
@jamesfredley jamesfredley merged commit 58ab00c into apache:8.0.x May 8, 2026
29 checks passed

Copilot AI 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.

Pull request overview

Restores Jackson Databind as the JSON runtime for the grails-forge-web-netty Micronaut service so Forge’s existing @Introspected DTOs can be serialized successfully (avoiding Micronaut Serde’s requirement for @Serdeable / @SerdeImport across returned types).

Changes:

  • Swap the runtime JSON dependency from io.micronaut.serde:micronaut-serde-jackson back to io.micronaut:micronaut-jackson-databind.
  • Add an in-file comment explaining why Serde cannot be used yet without an end-to-end DTO annotation/import migration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +43 to +47
// / prev-snapshot.grails.org. The rest of the forge stack (forge-api,
// forge-core, analytics-postgres) still depends on
// io.micronaut:micronaut-jackson-databind, so keep the netty service on
// the same JSON runtime to match. If forge ever migrates to Serde end-
// to-end, every DTO needs @Serdeable / @SerdeImport at the same time.
@bito-code-review

Copy link
Copy Markdown

Here's a reworded version of the comment in the build.gradle file to accurately reflect the dependency usage, incorporating your details about the other forge components.

grails-forge/grails-forge-web-netty/build.gradle

// Forge's grails-forge-api DTO classes (VersionDTO, ApplicationTypeDTO,
    // FeatureDTO, SelectOptionsDTO, Linkable, ...) are annotated with
    // @Introspected only. micronaut-serde-jackson requires @Serdeable on
    // each serializable type and rejects @Introspected-only classes at
    // runtime, which surfaces in the deployed native image as
    //   "Internal Server Error: Error encoding object [VersionDTO@..] to
    //    JSON: No serializable introspection present for type VersionDTO."
    // and breaks every JSON endpoint behind start.grails.org / next.grails.org
    // / prev-snapshot.grails.org. Other forge apps/tests still rely on
    // Jackson databind (e.g., grails-forge-core declares
    // com.fasterxml.jackson.core:jackson-databind, and grails-forge-api has
    // micronaut-jackson-databind as testRuntimeOnly), so keep the netty
    // service on the same JSON runtime to match. Serde migration needs
    // coordinated DTO annotations across the stack.
    runtimeOnly 'io.micronaut:micronaut-jackson-databind'

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants