Affected version(s)
- Spring Boot 3.4.0
Environment
-
JDK: Temurin 21.0.7 LTS
-
OS: Windows 11 (also reproducible on Linux)
-
Logging: Spring Boot structured logging (JsonWriter / JsonValueWriter)
Summary
When using Spring Boot’s JsonWriter.standard(), serializing a structure that contains a java.nio.file.Path inside a Map results in StackOverflowError. A standalone Path sometimes “looks fine”, but placing it inside a Map (or nested Maps) consistently triggers the error.
Root cause: Path implements Iterable
Minimal Reproducer (pure JsonWriter)
import org.junit.jupiter.api.Test;
import org.springframework.boot.json.JsonWriter;
import java.nio.file.Path;
import java.util.Map;
class JsonWriterPathStackOverflowReproducerTest {
@Test
void pathInsideMap_triggersStackOverflow() {
final Map<String, Object> payload = Map.of(
"details", Map.of(
"pfad-lokal", Path.of("/tmp/file_1"),
"pfad-remote", "file_1"
)
);
// This line throws StackOverflowError
final String json = JsonWriter.standard().writeToString(payload);
System.out.println(json);
}
}
Stack trace
java.lang.StackOverflowError
at java.base/java.nio.file.Path$1.<init>(Path.java:920)
at java.base/java.nio.file.Path.iterator(Path.java:920)
at java.base/java.lang.Iterable.forEach(Iterable.java:74)
at org.springframework.boot.json.JsonValueWriter.writeArray(JsonValueWriter.java:169)
at org.springframework.boot.json.JsonValueWriter.write(JsonValueWriter.java:118)
at org.springframework.boot.json.JsonValueWriter.writeElement(JsonValueWriter.java:190)
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
at org.springframework.boot.json.JsonValueWriter.writeArray(JsonValueWriter.java:169)
at org.springframework.boot.json.JsonValueWriter.write(JsonValueWriter.java:118)
... (repeats)
Root cause analysis
-
java.nio.file.Path implements Iterable
(its iterator yields name elements). -
In JsonValueWriter.write(V value), the type dispatch checks value instanceof Iterable<?> before other “scalar-like” fallbacks.
-
When a Path appears as a value inside a Map, JsonValueWriter goes into the Iterable branch and calls writeArray(...).
-
Each element yielded by the Path iterator is again a Path, hence again Iterable, so the writer opens another array and recurses.
-
This repeats until the stack overflows.
By contrast, libraries like Jackson treat Path as a scalar and serialize it via toString() by default (ToStringSerializer), which is the expected behavior for logs.
Expected behavior
Path should be serialized as a string (e.g., "/tmp/file_1"), not as a JSON array, and without recursion.
Proposed Fix
Low-risk & explicit (recommended): Handle Path before the Iterable branch in JsonValueWriter.write(...):
// inside JsonValueWriter.write(V value)
else if (value instanceof java.nio.file.Path p) {
writeString(p); // p.toString()
}
else if (value instanceof Iterable<?> iterable) {
writeArray(iterable::forEach);
}
Workaround (for users)
Users can plug a ValueProcessor that converts Path to String before writing (works at any nesting depth):
JsonWriter<Map<String, Object>> safeWriter = JsonWriter.of(members -> {
members.applyingValueProcessor((path, value) ->
(value instanceof java.nio.file.Path p) ? p.toString() : value
);
members.add(); // pass-through
});
This workaround is robust, but the underlying default in JsonWriter.standard() still leads to StackOverflowError when not customized.
Comment From: nosan
It has already been fixed: https://github.com/spring-projects/spring-boot/pull/44507
Please upgrade your Spring Boot version. The fix is available starting from 3.4.5: https://github.com/spring-projects/spring-boot/releases/tag/v3.4.5
Comment From: philwebb
Thanks @nosan