Repository navigation
Encode setBody(String) bodies with String.getBytes - #2371
Merged
hyperxpro merged 1 commit intoNov 5, 2026
Merged
Conversation
A String request body was encoded with charset.encode(CharBuffer.wrap(body)). The wrapped CharBuffer has no backing array, so the JDK encoder walks it one char at a time through virtual get/put calls, and CharsetEncoder.encode starts at 1.1 bytes per char and grows by copying. String.getBytes encodes straight from the String's own array and uses the JDK's ASCII and Latin-1 fast paths. Both replace malformed and unmappable input with the charset's default replacement, so the bytes on the wire do not change. A test checks this for every charset the JDK can encode, including unpaired surrogates and charsets that write a byte order mark. JMH StringBodyEncodeBenchmark on JDK 17, UTF-8: 4 KB ASCII goes from 8.9 us to 0.3 us and 64 KB Latin-1 from 198 us to 60 us. For a String stored as UTF-16 (any char above U+00FF) getBytes encodes into a 3 bytes per char scratch array and trims it, so transient allocation grows (64 KB Cyrillic 217 KB -> 316 KB/op) while encoding is still faster and the retained body is exact-size instead of oversized. Claude Code on behalf of Pavel Ptashyts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hyperxpro
approved these changes
Nov 5, 2026
4 tasks done
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.
Summary
setBody(String)request bodies are now encoded withbody.getBytes(charset)instead ofcharset.encode(CharBuffer.wrap(body)).Problem
CharBuffer.wrap(String)returns a buffer with no backing array. The JDK encoder therefore takes its per-char buffer loop (virtualget/putwith bounds checks), not the array loop or theString.getBytesASCII/Latin-1 fast paths.CharsetEncoder.encodealso starts at 1.1 bytes per char and grows by copying. This runs once per request attempt, on the caller thread, and costs O(body length).Change
NettyRequestFactory.body(...)builds aNettyByteArrayBodyfromgetBytes(charset). Before, it built aNettyByteBufferBodyaround the encodedByteBuffer. Both areNettyDirectBody: same content length, unpooled heapByteBuf, no content-type override. The HTTP/1.1 and HTTP/2 paths and replays read the body from theDefaultFullHttpRequestcontent, so they behave the same.Both APIs replace malformed and unmappable input with the charset's default replacement.
NettyRequestFactoryStringBodyTestchecks byte-for-byte equality throughnewNettyRequestfor every charset the JDK can encode, with unpaired surrogates, BOM and replacement chars, and UTF-16/UTF-32 (BOM writing). The same sweep in a standalone check on JDK 17, 21 and 25 showed 0 differences.Netty's
ByteBufUtil.writeUtf8with an exact-size array was also tried and rejected: for a high surrogate followed by a valid pair it emits???where the JDK emits?plus the pair.HttpUtils.urlEncodeFormParamsis left alone. It encodes a pooledStringBuilder, not aString, sogetByteswould need an extratoString().Benchmark
StringBodyEncodeBenchmark(new), JDK 17, UTF-8,-f 2 -wi 3 -i 5 -prof gc. Length is in chars.mixedis ASCII JSON ending in one emoji, so the String is stored as UTF-16.Trade-off: for a String stored as UTF-16 (any char above U+00FF),
getBytes(UTF_8)encodes into a scratch array of 3 bytes per char and then trims it. Transient allocation therefore grows for that text (same on JDK 21 and 25). Encoding is still faster, and the body kept for the request is exact-size; before, it was the encoder's oversized buffer.The host was shared with other builds, so take the timings as indicative. B/op is deterministic.
AI disclosure
Claude Code on behalf of Pavel Ptashyts. The commit includes
Co-Authored-ByperAGENTS.md.Test plan
NettyRequestFactoryStringBodyTest: 4 tests pass.mvnw.cmd -B -ntp -Dgpg.skip=true clean verifyon JDK 17 (no JDK 11 was available on the host): 1,861 tests, 0 failures, 0 errors, 35 skipped; Revapi passed. Source/target is 11. JDK 11 coverage comes from CI.🤖 Generated with Claude Code