Skip to content

fix: use custom Gson deserializers to avoid Java 26+ final field mutation warnings#2434

Open
maxandersen wants to merge 1 commit intojbangdev:mainfrom
maxandersen:2417-java26-final-fields
Open

fix: use custom Gson deserializers to avoid Java 26+ final field mutation warnings#2434
maxandersen wants to merge 1 commit intojbangdev:mainfrom
maxandersen:2417-java26-final-fields

Conversation

@maxandersen
Copy link
Copy Markdown
Collaborator

Fixes #2417

Problem

Java 26 introduced warnings when Gson uses reflection to mutate final fields during deserialization:

WARNING: Final field catalogRef in class dev.jbang.catalog.CatalogRef has been mutated 
reflectively by class com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$2
WARNING: Mutating final fields will be blocked in a future release

Future Java versions will block this entirely, breaking JBang's catalog deserialization.

Solution

This PR replaces Gson's default reflection-based deserialization with custom JsonDeserializer implementations for all catalog data classes:

  • CatalogRefDeserializer - handles catalog-ref with alternate names
  • TemplateDeserializer - handles file-refs and properties
  • AliasDeserializer - handles 29 fields including runtime options, dependencies, etc.
  • JavaAgentDeserializer - handles nested JavaAgent structure
  • CatalogDeserializer - handles top-level catalog with nested maps

These deserializers use constructor-based initialization instead of reflection, properly handle all @SerializedName annotations, and use TypeToken for correct generic type handling.

Benefits

No reflection warnings on Java 26+
Future-proof - won't break when Java blocks final field mutation
Keeps final fields for immutability and thread-safety
GraalVM native-image friendly
Maintains compatibility with Java 8+

Testing

  • Tested successfully with Java 26 - no warnings:
    $ jbang run --java 26 hello@jbangdev world
    Hello world
    # No warnings!
  • All existing tests pass (catalog, alias, template tests)
  • Comparison test demonstrates the difference between reflection and custom deserializers

Comparison with PR #2430

This approach is superior to the manifest workaround in #2430:

Aspect #2430 (Manifest) This PR (Custom Deserializers)
Future-proof ❌ Temporary workaround ✅ Permanent fix
Follows Gson best practices
Will work when Java blocks mutation

This is the recommended Gson pattern for immutable objects with final fields.

…tion warnings

Fixes jbangdev#2417

Java 26 introduced warnings when Gson uses reflection to mutate final fields during
deserialization. Future Java versions will block this entirely.

This commit replaces Gson's default reflection-based deserialization with custom
JsonDeserializer implementations for all catalog data classes:
- CatalogRefDeserializer
- TemplateDeserializer
- AliasDeserializer
- JavaAgentDeserializer
- CatalogDeserializer

Benefits:
- No reflection warnings on Java 26+
- Future-proof (won't break when Java blocks final field mutation)
- Keeps final fields for immutability and thread-safety
- GraalVM native-image friendly
- Maintains compatibility with Java 8+

All existing tests pass. Tested successfully with Java 26.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@maxandersen
Copy link
Copy Markdown
Collaborator Author

@quintesse you prefer this or we do the flag?

@quintesse
Copy link
Copy Markdown
Contributor

I'm okay with the flag, because this a quite a bit of code. I'm guessing the flag will be okay to use for quite some time. If they ever deprecate it we can still use this solution.

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.

JBang and Java 26 final fields

2 participants