WritableJson.toByteArray() may corrupt supplementary characters - #51464
Open
yyuneu wants to merge 1 commit into
Open
WritableJson.toByteArray() may corrupt supplementary characters#51464yyuneu wants to merge 1 commit into
yyuneu wants to merge 1 commit into
Conversation
A high surrogate left unconsumed by the encoder was dropped between append calls, so surrogate pairs written a character at a time by JsonValueWriter were replaced with `?`. See spring-projectsgh-51464 Signed-off-by: JaeHyunAn <98042706+yyuneu@users.noreply.github.com>
yyuneu
force-pushed
the
fix/appendable-byte-array-surrogate
branch
from
August 26, 2026 10:13
74651ef to
9396527
Compare
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.
What happened?
AppendableByteArrayencodes each append call independently withendOfInputset tofalse. If the input ends with a high surrogate,CharsetEncoderreturnsUNDERFLOWwithout consuming it. Because that input buffer is then discarded, the high surrogate is lost. The low surrogate in the next append call is malformed on its own and is replaced with?.JsonValueWriterwrites strings one UTF-16 code unit at a time, so this affects supplementary characters, including many emoji. For example:Structured logging is also affected.
StructuredLogEncoderfor Logback andStructuredLogLayoutfor Log4j2 useformatAsBytes, which reachesWritableJson.toByteArray(Charset).This regression was introduced by #49428 in 4.1.0. The previous implementation used an
OutputStreamWriter, which retains a pending high surrogate across writes, so 4.0.x is not affected.What does this PR change?
AppendableByteArraynow retains an unconsumed high surrogate and prepends it to the input from the next append call.If no further input arrives,
toByteArray()encodes the pending surrogate withendOfInputset totrue, so an unpaired high surrogate is replaced instead of being dropped.reset()also clears the pending state so it cannot carry over when the cached instance is reused.There are no public API changes.
Tests
Added coverage for:
WritableJsonThe
AppendableByteArraytests compare the result with the output produced byOutputStreamWriter. All of these tests fail without this change.:core:spring-boot:checkpasses on Linux with JDK 25.Related: #51156 fixed a separate regression introduced by #49428.