Skip to content

Reset cached AppendableByteArray before it is reused - #51156

Open
skdas20 wants to merge 1 commit into
spring-projects:mainfrom
skdas20:fix-51154-appendable-byte-array-reset
Open

Reset cached AppendableByteArray before it is reused#51156
skdas20 wants to merge 1 commit into
spring-projects:mainfrom
skdas20:fix-51154-appendable-byte-array-reset

Conversation

@skdas20

@skdas20 skdas20 commented Jul 29, 2026

Copy link
Copy Markdown

See gh-51154.

AppendableByteArray.reset() is only reached from toByteArray():

byte[] result = new byte[size];
System.arraycopy(...);
reset();          // only on the success path
return result;

so when to(appendable) in WritableJson.toByteArray(Charset) throws part-way through, the partially written bytes stay in the instance held by the ThreadLocal cache, and get() hands that dirty instance to the next caller on the same thread. In structured logging that means one failed event corrupts the next event into invalid JSON, as reported.

This resets the cached instance when it is handed out rather than when it is drained, so the buffer is clean regardless of how the previous use ended. That also covers the early return in toByteArray():

if (size <= 0) {
    return NO_BYTES;   // returns without reset(), leaving the buffer flipped
}

which the alternative (resetting on the failure path in WritableJson) would not.

Tests added:

  • AppendableByteArrayTests.getWhenPreviousUseWasAbandonedReturnsCleanInstance — covers the cache contract directly: append to an instance, never drain it, then assert the next get() returns it clean.
  • WritableJsonTests.toByteArrayWhenPreviousWriteFailedDoesNotIncludePartialContent — the reported end-to-end shape: a failed encode followed by an unrelated writer on the same thread.

Both fail on main and pass with the change. org.springframework.boot.json.*, the logging.structured tests, and checkFormatMain/checkFormatTest are all green locally.

I raised the analysis on the issue first and offered either placement — happy to move the reset into WritableJson.toByteArray instead if you'd prefer to keep get() free of side effects, or to close this if you'd rather fix it in-house given it's a regression in recent code.

AppendableByteArray.reset() was only called from toByteArray(), so an
encode that failed part-way left its partial output in the thread-local
cached instance. The next value encoded on that thread was then prefixed
with it, which in structured logging corrupted the log event following a
failed one.

Reset the cached instance when it is handed out instead, so the buffer is
clean regardless of how the previous use ended. This also covers the
early return in toByteArray() for empty content, which returns without
resetting.

See spring-projectsgh-51154

Signed-off-by: Sumit Kumar Das <skdas5405@gmail.com>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants