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, and JsonValueWriter.write(...) prioritizes the Iterable branch. The writer enters a self-feeding loop where each Path element is again treated as an Iterable, causing unbounded recursion.

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