Skip to content

JsonWriter causes StackOverflowError when serializing java.nio.file.Path inside a Map #47765

@nikakoy-png

Description

@nikakoy-png

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions