feat: improve transformation pipeline performance using Java 17 idioms#665
Merged
fborriello merged 12 commits intomasterfrom Apr 27, 2026
Merged
feat: improve transformation pipeline performance using Java 17 idioms#665fborriello merged 12 commits intomasterfrom
fborriello merged 12 commits intomasterfrom
Conversation
- ConversionProcessorFactory: replace 12-branch if-else chain with switch expression on class name; cache processor singletons as static constants to eliminate per-call object allocation - ConversionAnalyzer: replace 12-branch if-else chain in getTypeConversionFunction with switch expression on source type name, removing redundant static imports - ConverterImpl: use identity (==) check first before name comparison to short-circuit same-type pass-through without string allocation - ReflectionUtils: replace AtomicReference allocation in getRealTarget with instanceof pattern matching; eliminate redundant isAnnotationPresent call before getAnnotation; use instanceof pattern in getGenericClassType; use early-return in handleReflectionException - ClassUtils: replace stream().anyMatch() in isSpecialType/isCustomSpecialType with plain for-loops to avoid lambda allocation on type checks; replace LinkedList with ArrayList in getMethods for better cache locality; replace collect(toList()) with toList() (Java 16) where result is consumed read-only; replace forEach+collect with forEach(res::add) in getPrivateFinalFields, getPrivateFields, getMethods - AbstractTransformer: eliminate stream+lambda in withFieldMapping and withFieldTransformer inner loops, replacing with plain for-each - AbstractBeanTransformer: replace asList+addAll with direct for-each add in skipTransformationForField to avoid intermediate List allocation - MapPopulator: replace keySet().stream().collect(toMap(...)) with entrySet() imperative loop and pre-sized HashMap; apply instanceof pattern matching to eliminate getClass().equals() calls in isPrimitive and getElemValue Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Test Coverage Report
|
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Adds bull-benchmark module with 7 microbenchmarks covering: - mutable/immutable simple bean transformation (5 fields, setter vs constructor path) - mutable/immutable complex bean transformation (nested objects, lists, maps) - type conversion hot paths: int->long, String->int, Boolean->BigDecimal Run with: java -jar bull-benchmark/target/benchmarks.jar Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Adds docs/site/markdown/transformer/bean/benchmarks.md covering: - what each of the 7 JMH benchmarks measures - how to build the fat jar - how to run (full, quick, single, with options) - how to compare two branches with JSON output - how to interpret Score/Error/Cnt columns - how to add a new benchmark Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Adds benchmark.sh at the project root that builds the fat jar and runs JMH in a single command; any arguments are forwarded to JMH. Updates benchmarks.md to reference the script and simplify the Running and Comparing sections. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…k fat jar - Generate JMH harness sources into target/generated-sources/jmh (separate from classes dir) then compile them in prepare-package phase so *_jmhTest classes are present in the fat jar before shading - Declare slf4j-api at compile scope to override the parent dependencyManagement lock to test scope, ensuring LoggerFactory is included in the shaded jar Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
After JMH finishes, the script now prints a concise formatted table of benchmark names, scores, error intervals, and units — making results readable at a glance without scrolling through the full JMH output. Handles both single-iteration (no error column) and multi-iteration (± error) JMH output formats. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Redirect Maven build stdout/stderr to a temp log (only shown on failure) and filter NOTE:/WARNING:/Processing/Writing lines from the JMH forked-JVM output, which originate from JDK_JAVA_OPTIONS and sun.misc.Unsafe deprecation warnings outside our control. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…lumn - Convert scores from µs/op to ms/op for readability - Split summary into Bean transformations and Type conversions sections - Show ± confidence interval in Error column when available Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- getNotFinalFields: wrap toList() in new ArrayList to keep the cached list mutable, preventing UnsupportedOperationException if callers mutate it - getRealTarget: document that empty Optional unwraps to null intentionally Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
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.
Description
ConversionProcessorFactory: replace 12-branch if-else chain with switch expression on class name; cache processor singletons as static constants to eliminate per-call object allocationConversionAnalyzer: replace 12-branch if-else chain in getTypeConversionFunction with switch expression on source type name, removing redundant static importsConverterImpl: use identity (==) check first before name comparison to short-circuit same-type pass-through without string allocationReflectionUtils: replace AtomicReference allocation in getRealTarget with instanceof pattern matching; eliminate redundant isAnnotationPresent call before getAnnotation; use instanceof pattern in getGenericClassType; use early-return in handleReflectionExceptionClassUtils: replacestream().anyMatch()in isSpecialType/isCustomSpecialType with plain for-loops to avoid lambda allocation on type checks; replace LinkedList with ArrayList in getMethods for better cache locality; replace collect(toList()) with toList() (Java 16) where result is consumed read-only; replace forEach+collect with forEach(res::add) in getPrivateFinalFields, getPrivateFields, getMethodsAbstractTransformer: eliminate stream+lambda in withFieldMapping and withFieldTransformer inner loops, replacing with plain for-eachAbstractBeanTransformer: replace asList+addAll with direct for-each add in skipTransformationForField to avoid intermediate List allocationMapPopulator: replace keySet().stream().collect(toMap(...)) with entrySet() imperative loop and pre-sized HashMap; apply instanceof pattern matching to eliminate getClass().equals() calls in isPrimitive and getElemValueChecklist:
feature: Feature I'm adding or expandingbug: Bugfix or experimentwip: Work in progress; stuff I know won't be finished soon