From dfc9f7a858e95d9d80c09fe870701f67481cb3a8 Mon Sep 17 00:00:00 2001 From: Sumit Kumar Das Date: Wed, 29 Jul 2026 16:46:02 +0000 Subject: [PATCH] Reset cached AppendableByteArray before it is reused 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 gh-51154 Signed-off-by: Sumit Kumar Das --- .../boot/json/AppendableByteArray.java | 6 ++++++ .../boot/json/AppendableByteArrayTests.java | 12 ++++++++++++ .../springframework/boot/json/WritableJsonTests.java | 11 +++++++++++ 3 files changed, 29 insertions(+) diff --git a/core/spring-boot/src/main/java/org/springframework/boot/json/AppendableByteArray.java b/core/spring-boot/src/main/java/org/springframework/boot/json/AppendableByteArray.java index a5f271352d28..5f212ce915d3 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/json/AppendableByteArray.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/json/AppendableByteArray.java @@ -129,6 +129,12 @@ static AppendableByteArray get(Charset charset) { result = new AppendableByteArray(charset); cache.set(new SoftReference<>(result)); } + else { + // The cached instance is reused, so it must be clean before it is + // handed out again. A previous use may have been abandoned part-way, + // for example when writing the value threw, leaving content behind. + result.reset(); + } return result; } diff --git a/core/spring-boot/src/test/java/org/springframework/boot/json/AppendableByteArrayTests.java b/core/spring-boot/src/test/java/org/springframework/boot/json/AppendableByteArrayTests.java index 22cb350b450a..c2195ea438fa 100644 --- a/core/spring-boot/src/test/java/org/springframework/boot/json/AppendableByteArrayTests.java +++ b/core/spring-boot/src/test/java/org/springframework/boot/json/AppendableByteArrayTests.java @@ -68,6 +68,18 @@ void writeUsingCache() throws IOException { assertByteArray(StandardCharsets.UTF_8, AppendableByteArray::get, (appendable) -> appendable.append(string)); } + @Test + void getWhenPreviousUseWasAbandonedReturnsCleanInstance() throws IOException { + AppendableByteArray abandoned = AppendableByteArray.get(StandardCharsets.UTF_8); + abandoned.append("partial content"); + // The instance is never converted to a byte array, as happens when writing + // the value throws part-way through. + AppendableByteArray reused = AppendableByteArray.get(StandardCharsets.UTF_8); + assertThat(reused).isSameAs(abandoned); + reused.append("clean"); + assertThat(reused.toByteArray()).isEqualTo("clean".getBytes(StandardCharsets.UTF_8)); + } + private void assertByteArray(Charset charset, ThrowingConsumer action) throws Exception { assertByteArray(4, 4, charset, action); } diff --git a/core/spring-boot/src/test/java/org/springframework/boot/json/WritableJsonTests.java b/core/spring-boot/src/test/java/org/springframework/boot/json/WritableJsonTests.java index aa3ea133f04c..6d7bfbc1a602 100644 --- a/core/spring-boot/src/test/java/org/springframework/boot/json/WritableJsonTests.java +++ b/core/spring-boot/src/test/java/org/springframework/boot/json/WritableJsonTests.java @@ -66,6 +66,17 @@ void toByteArrayReturnsByteArray() { assertThat(writable.toByteArray()).isEqualTo("{}".getBytes()); } + @Test + void toByteArrayWhenPreviousWriteFailedDoesNotIncludePartialContent() { + WritableJson failing = (out) -> { + out.append("{\"partial\":"); + throw new IllegalStateException("bad"); + }; + assertThatExceptionOfType(IllegalStateException.class).isThrownBy(failing::toByteArray); + WritableJson healthy = (out) -> out.append("{\"ok\":true}"); + assertThat(healthy.toByteArray()).isEqualTo("{\"ok\":true}".getBytes(StandardCharsets.UTF_8)); + } + @Test void toResourceWritesJson() throws Exception { File file = new File(this.temp, "out.json");