Repository navigation
Blackbird: use generated setters for builder-based deserialization - #364
Merged
cowtowncoder merged 35 commits intoOct 30, 2026
Merged
Conversation
Member
|
@stevenschlansker WDYT? Obviously only matters pre-rewrite but could merge in 2.x / merge forward to 3.1, 3.2, if this makes sense. |
cowtowncoder
left a comment
Member
There was a problem hiding this comment.
A few issues found while reviewing; details inline.
Claude Code on behalf of @cowtowncoder
…/blackbird-builder-setters # Conflicts: # blackbird/src/main/java/com/fasterxml/jackson/module/blackbird/deser/BBDeserializerModifier.java # blackbird/src/test/java/com/fasterxml/jackson/module/blackbird/deser/TestInaccessibleSetterTypes.java # blackbird/src/test/java/com/fasterxml/jackson/module/blackbird/deser/otherpackage/ModeBeanBase.java # blackbird/src/test/java/com/fasterxml/jackson/module/blackbird/util/TestReflectionHack.java
This was referenced Oct 29, 2026
Contributor
Author
|
Thanks for the review! Fixing it grew the change quite a bit, so I split it into three stacked PRs, to be merged in order:
Each PR's diff includes the earlier ones until they're merged, so #366 is the place to start. |
…/blackbird-builder-setters # Conflicts: # release-notes/CREDITS-2.x # release-notes/VERSION-2.x
…/blackbird-builder-setters
Member
|
+1 for splitting into stacked PRs! I'll need to weave changes wrt #359 that goes in 3.x so this might slightly simplify that too. |
2.x has FasterXML#366 squashed; this branch already had it up to 2806ae1, so resolve against that rather than the older merge base. Keep both release notes entries and both sets of tests; builder tests now use `_mapperWithLookupIn`, and expect inherited protected builder setters to be optimized on JDK 14+ as for beans. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
databind also calls set() on builders, for properties buffered before a property-based creator and for external type ids, so whether a setter failure was retried, how it was reported, and whether access modifiers were honored depended on the order of properties in the JSON. - Builder properties now report failures from set() like setAndReturn(): no retry through reflection, wrapped as MethodProperty does - A builder setter Jackson wouldn't make accessible is left to reflection entirely, not just for setAndReturn() - Primitive setAndReturn() checks for a generated setter before unboxing Regular beans keep their existing behavior (see TestAccessFallback). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Treat a builder without a build method (`buildMethodName=""`) as a builder too, by also checking for its builder config - Leave a builder setter to reflection only if reflection itself couldn't call it: a public setter of a public class in an exported package stays optimized even with OVERRIDE_PUBLIC_ACCESS_MODIFIERS disabled - Keep calling the 3-arg findOptimizableProperties() for regular beans, so subclasses overriding it still work - set() skips nulls for Nulls.SKIP as MethodProperty does, and primitive set() leaves a null to the delegate instead of throwing a raw NPE Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
…ible databind makes some setters accessible before Blackbird sees them (for injection or back references). `unreflect` then skips its access checks, so a package-private setter inherited from another package passed, and creating the generated accessor failed the whole deserializer. Unreflect a fresh copy instead, so such setters are left to reflection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- isMadeAccessible() has the property fix its access, as databind does when building the deserializer, instead of re-implementing its rules - Non-void setters adapt their generated thunk with a plain lambda rather than spinning a trampoline class per property (about 30-40% faster deserializer construction for a 20-setter builder) - Pass the setter's value type to SettableObjectProperty, and move _reflectiveSetAndReturn there as a private helper - Drop the unused value argument of _reportProblemAndReturn - Remove tests for the stack-trace retry heuristic, which no longer exists - Benchmarks: use the current parent version, so they measure this code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A void setter's generated consumer is adapted to a thunk returning null, so setAndReturn() keeps the same builder (as MethodProperty does) instead of going through reflection; a missing thunk now only means the setter is left to reflection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
set() only calls the generated setter with a value of the setter's own type, on an instance it can take; anything else goes through reflection, so it fails (or is widened) as without Blackbird, instead of throwing a ClassCastException. This also stops int/long setters from silently truncating another Number type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cowtowncoder
approved these changes
Oct 30, 2026
- Only builder properties get the setAndReturn thunk and the access gate (with its early fixAccess); regular beans leave setAndReturn to reflection, as before this branch - set() only checks the instance type for builders, keeping that check off the hot path of regular beans Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… error The warning claimed Blackbird deserialization was disabled for the property, but the generated setter keeps being used and falls back to reflection on each such error; only the warning is logged once. Reword it, and rename the flag that only guards the logging. No behavior change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cowtowncoder
added a commit
to parker89/jackson-modules-base
that referenced
this pull request
Oct 30, 2026
Keeps 2.x's squashed builder-setter changes (FasterXML#364) and re-applies this branch's no-retry and null-handling changes on top. TestSetterFailures is updated for 2.x's SettableObjectProperty constructor and removal of BBDeserializerModifier.trampoline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cowtowncoder
added a commit
that referenced
this pull request
Oct 30, 2026
Ports #364 (Blackbird: use generated setters for Builder-based deserialization) to 3.x, following 3.x `MethodProperty`, which calls setters through a method handle from databind's own module: - Setter failures are reported with `_throwAsJacksonE(p, e, value)` as is (no `InvocationTargetException` wrapping; Errors are rethrown as they are) - Values or builders the generated setter can't take are left to the delegate, which fails as without Blackbird - A public builder setter Jackson didn't make accessible is only optimized if databind's module reads its module and has its package exported to it Tests adapted accordingly; the `TextNode(null)` test is dropped, as `StringNode` rejects null in 3.x. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cowtowncoder
added a commit
to stevenschlansker/jackson-modules-base
that referenced
this pull request
Oct 30, 2026
Brings in the 2.x port of generated builder setters (FasterXML#364) for the old LambdaMetafactory engine. This branch's generated-codec engine already handles builders, so its BBDeserializerModifier is kept and the Settable*Property classes stay deleted. The new builder tests are adapted to it: they check for a generated reader instead of optimized properties, drop the cases that construct the removed property classes, and expect the List property (a stock arm) to go through reflection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cowtowncoder
added a commit
that referenced
this pull request
Oct 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builder deserialization currently goes through
MethodProperty, even though Blackbird already creates an accessor that returns the setter's result. Keep that accessor and use it fromsetAndReturnanddeserializeSetAndReturn.Second in the stack: #366 → #364 → #367. #366 is merged, so this diff now has only the builder change.
A setter's non-null result becomes the next builder; a null result keeps the current builder. Primitive int, long and boolean arguments avoid boxing. Void setters, incompatible values or receivers, and setters Jackson leaves subject to normal access checks continue through reflection. Builder properties that databind assigns through
set()(buffered before a property-based creator, or external type ids) are handled the same way, so the outcome doesn't depend on the order of properties in the JSON. Null handling and setter error reporting matchMethodProperty, including JSON location. (Builder setter failures already lacked the location before this PR, sinceMethodProperty.setAndReturnhas no parser either.)Once the generated setter is called, a failure is never retried, so a setter never runs twice. Telling a failure of the generated accessor apart from the setter's own would mean guessing from the stack trace, so both are reported like a setter failure. #366 prevents the known accessor failures at setup. The trade-off is that an accessor failure those checks miss now fails deserialization instead of falling back to reflection. #367 does the same for regular bean setters.
Tests verify that mutable and copying builders use generated setters, compare edge cases and failures with vanilla Jackson, and check that real JVM access failures are reported without running the setter. All Blackbird tests pass on Java 8, 11, 17 and 25.
The new
builderArrayDeserbenchmark deserializes 1,000 beans per operation. On JDK 25, allocation fell from 249.5 to 209.5 KB/op, mostly from avoiding primitive boxing. These measurements predate the review changes that added receiver/type checks. Throughput varied too much on my laptop to quote a reliable improvement.Benchmark commands (use a disposable checkout)
CLA submitted by email.