Skip to content

spring-boot-starter: fix configuration binding with Jackson 2.21 - #151

Open
JingkaiTang wants to merge 1 commit into
trpc-group:JDK17/Springboot3from
JingkaiTang:fix/jackson-naming-strategy
Open

JingkaiTang wants to merge 1 commit into
trpc-group:JDK17/Springboot3from
JingkaiTang:fix/jackson-naming-strategy

Conversation

@JingkaiTang

@JingkaiTang JingkaiTang commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #150

TRpcConfigurationBindResult fails during class initialization with NoSuchFieldError: SNAKE_CASE when an application uses Jackson Databind 2.21.4. Use PropertyNamingStrategies.SNAKE_CASE in place of the removed PropertyNamingStrategy.SNAKE_CASE field, preserving snake_case configuration keys and user-provided extension-map keys.

Add two regression tests to the starter module's normal test source tree. Dependency versions are unchanged.

Jackson compatibility

PropertyNamingStrategies.SNAKE_CASE has been available since Jackson Databind 2.12, as documented in the 2.12 API. The current default Jackson versions declared by both tRPC-Java branches already provide this API:

Branch Default Jackson version Source
JDK17/Springboot3 2.18.2 Dependency BOM
master 2.15.4 Dependency BOM

Therefore, this replacement requires no dependency upgrade for either branch's default dependency set; this PR leaves all POM versions unchanged. This is an API-availability statement, not a claim that every Jackson release or the master branch's full application suite has been tested. The actual tested versions are listed below. Consumers overriding Jackson below 2.12 cannot use the new API.

Validation

Tested on macOS/aarch64 with Amazon Corretto 17.0.20.1 and 21.0.5, using the native Maven POM and the starter's 19-node dependency reactor. The two new tests pass on both JDKs (0 failures, errors, or skips). JDK 17 validation includes a clean rebuild.

mvn -B -ntp \
  -pl trpc-spring-boot-starters/trpc-spring-boot-starter -am \
  -Dtest=TRpcConfigurationBindResultTest \
  -Dsurefire.failIfNoSpecifiedTests=false clean test

The two regression methods were also invoked directly in separate JVMs with the Jackson runtime jars substituted. On both JDK 17 and JDK 21, the results are:

Jackson core/databind annotations Original binder Fixed binder
2.15.2 2.15.2 Pass Pass
2.18.2 2.18.2 Pass Pass
2.21.4 2.21 NoSuchFieldError: SNAKE_CASE Pass

The original binder was compiled from base commit 6d2a46043757c3053745beaecbae9f7ebd0b9a83 as a control. These checks cover configuration conversion without starting a Spring context or RPC listener. The full repository suite and Checkstyle workflow have not run locally and remain for upstream CI.

Upstream CI status and Netty investigation (2026-10-19)

The original JDK 21 full-test job remains failed; no test or assertion in this PR has been skipped or weakened. JDK 17 full tests, Checkstyle, Maven Install (which skips tests), and Codecov passed. The failed reactor stopped in trpc-transport-netty before building the starter.

Scope and historical control

The base-to-head comparison contains only the binder replacement and its two regression tests. Core/Netty code, all POMs, and workflows are unchanged; the Netty reactor has no dependency on the starter. The preceding PR #149 JDK 21 job passed. Its head f765b458 and this PR's base 6d2a4604 have the same Git tree 6278b9c406f61066a47de12b1e1b3c215103bfd0.

What the original failure establishes

At the assertion the client had one active connection; client connect/disconnect counts were 1015/1014 and server counts 1772/1771. getChannels() filters by the underlying channel's active state, so this is not merely a stale inactive entry. The test reverses JUnit's expected/actual arguments (assertEquals(client.getChannels().size(), 0)), explaining the misleading expected 1 / actual 0 message. Its closed=2500 counter counts cancellation/close-callback scheduling, not completed channel closes. The fixed five-second sleep and unobserved close stages cannot establish successful asynchronous cleanup.

Independently reproduced pre-existing pool race

A deterministic public-API resource-ownership test runs the real AbstractClientTransport, with mocked channels and a barrier-controlled lock. Two concurrent getChannel() calls contend for one lazy pool slot. After transport.close(), one created channel remains open on both base and head, on both JDK 17 and JDK 21.

The cause is in ensureChannelActive: it reads/checks the slot before taking connLock, then replaces it without rereading/rechecking. Two contenders can both retain the old item, publish distinct new items, and both close only the old item, orphaning the overwritten new connection.

Eight-way red/green validation:

Source JDK17 original JDK21 original JDK17 lock/recheck candidate JDK21 lock/recheck candidate
Base 6d2a4604 Fail: 1 leaked Fail: 1 leaked Pass Pass
Head 1b027145 Fail: 1 leaked Fail: 1 leaked Pass Pass

The candidate moves slot lookup, availability/connecting checks, replacement, and retirement of the old item under the existing lock. It is applied only in isolated CI runners; PR #151 is unchanged. This demonstrates a real existing pool race, not a JDK21-only defect or a binder regression. It is a plausible explanation of the original CI symptom; the original run has no per-channel trace proving this exact interleaving, so it is not claimed as conclusively attributed.

Original-test repetition and candidate validation

The unchanged original test was run 20 times per source/JDK combination, each Maven invocation starting a fresh test JVM:

Source JDK17 failures JDK21 failures
Base 6d2a4604 0/20 0/20
Head 1b027145 1/20 (attempt 10) 2/20 (attempts 5 and 17)

All three failures were the original line-113 assertion with one active connection. This directly reproduces the original symptom on JDK17 as well as JDK21. These small, independently scheduled runner samples are not a reliable comparison of failure rates. The exact original stress test did not fail in the baseline sample; baseline reproduction above is the deterministic ownership test, not a claim of a baseline stress-test failure. The core/Netty sources and their build/dependency inputs are identical across base/head, and the starter is outside this targeted reactor.

Minimal original-test command (repeat in fresh Maven invocations):

mvn -B -ntp -pl trpc-transport/trpc-transport-netty -am \
  '-Dtest=NettyChannelTest#testTrpcNetty' \
  -Dsurefire.failIfNoSpecifiedTests=false test

Candidate validation on the baseline passed on both JDK17 and JDK21: 557 Core tests (including the deterministic regression) + 40 Netty tests, zero failures/errors/skips. This is module-level validation, not a full-repository validation or a performance benchmark. The wider critical section could affect contention and deserves review before a production fix.

Conclusion: a pre-existing, cross-JDK connection-pool race is independently demonstrated; no causal path from this starter-only PR to that race was found. The precise interleaving of the original upstream failure remains untraced. Keep a pool fix separate from this Jackson PR, retain the existing assertions, and do not treat longer sleeps as a fix for an orphaned connection.

The integration returned HTTP 403 when asked to rerun the original upstream job, so its failed check remains unchanged. All diagnostic workflows live on separate branches in the author's fork and do not change this PR's head commit.

@codecov

codecov Bot commented Oct 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.78188%. Comparing base (6d2a460) to head (1b02714).

Additional details and impacted files

Impacted file tree graph

@@                      Coverage Diff                      @@
##             JDK17/Springboot3        #151         +/-   ##
=============================================================
+ Coverage             85.67224%   85.78188%   +0.10963%     
- Complexity                 446         450          +4     
=============================================================
  Files                      558         558                 
  Lines                    17330       17330                 
  Branches                  1509        1509                 
=============================================================
+ Hits                     14847       14866         +19     
+ Misses                    2483        2464         -19     
Flag Coverage Δ
Merge ?
pull-request 85.78188% <100.00000%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...onfiguration/bind/TRpcConfigurationBindResult.java 76.92308% <100.00000%> (ø)

... and 12 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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