fix: Strip only a single leading space from SSE data field (#5532)

## Issue
Closes #5531

## Change
`DefaultServerSentEventParser` applied `content.trim()` to each `data:`
field value, removing all leading and trailing whitespace. The WHATWG
HTML Living Standard (Server-sent events, "Interpreting an event
stream") requires removing only a single leading U+0020 SPACE when
present; further leading whitespace and all trailing whitespace must be
preserved. The old behavior corrupted values with multiple leading
spaces, trailing spaces, or indentation, which matters for LLM token
streaming, the module's primary use.

This PR replaces `trim()` with a single-leading-space strip on the
`data:` branch only; `event:` handling is left unchanged to keep the
change minimal.

Added unit tests for preserving additional leading whitespace,
preserving trailing whitespace, and leaving values with no leading space
unchanged. Existing parameterized and multi-line tests remain green
(each line still loses one leading space).

Could a maintainer confirm whether the original `trim()` was intentional
normalization rather than a bug?

## General checklist
- [x] There are no breaking changes (API, behaviour)
- [x] I have added unit and/or integration tests for my change
- [x] The tests cover both positive and negative cases
- [x] I have manually run all the unit and integration tests in the
module I have added/changed, and they are all green
- [ ] I have manually run all the unit and integration tests in the
[core](https://github.com/langchain4j/langchain4j/tree/main/langchain4j-core)
and
[main](https://github.com/langchain4j/langchain4j/tree/main/langchain4j)
modules, and they are all green
- [ ] I have added/updated the
[documentation](https://github.com/langchain4j/langchain4j/tree/main/docs/docs)
- [ ] I have added an example in the [examples
repo](https://github.com/langchain4j/langchain4j-examples) (only for
"big" features)
- [ ] I have added/updated [Spring Boot
starter(s)](https://github.com/langchain4j/langchain4j-spring) (if
applicable)

<!-- Not applicable: no new maven module, no embedding store integration
changes. -->
This commit is contained in:
Eunbin Son 2026-07-31 22:53:18 +09:00 committed by GitHub
parent 6d1af7497a
commit ba36ad4eeb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 48 additions and 1 deletions

View File

@ -36,10 +36,15 @@ public class DefaultServerSentEventParser implements ServerSentEventParser {
event = line.substring("event:".length()).trim();
} else if (line.startsWith("data:")) {
String content = line.substring("data:".length());
// Per the WHATWG HTML Living Standard (Server-sent events), if the field value
// starts with a single U+0020 SPACE, only that one space is removed.
if (content.startsWith(" ")) {
content = content.substring(1);
}
if (!data.isEmpty()) {
data.append("\n");
}
data.append(content.trim());
data.append(content);
}
}

View File

@ -130,6 +130,48 @@ class DefaultServerSentEventParserTest {
verify(listener, never()).onEvent(any());
}
@Test
void shouldPreserveAdditionalLeadingWhitespaceInData() {
// given
String input = "data: indented\n\n";
InputStream stream = new ByteArrayInputStream(input.getBytes(UTF_8));
// when
parser.parse(stream, listener);
// then
verify(listener).onEvent(eq(new ServerSentEvent(null, " indented")), any());
}
@Test
void shouldPreserveTrailingWhitespaceInData() {
// given
String input = "data: trailing \n\n";
InputStream stream = new ByteArrayInputStream(input.getBytes(UTF_8));
// when
parser.parse(stream, listener);
// then
verify(listener).onEvent(eq(new ServerSentEvent(null, "trailing ")), any());
}
@Test
void shouldNotRemoveAnyCharacterWhenDataHasNoLeadingSpace() {
// given
String input = "data:nospace\n\n";
InputStream stream = new ByteArrayInputStream(input.getBytes(UTF_8));
// when
parser.parse(stream, listener);
// then
verify(listener).onEvent(eq(new ServerSentEvent(null, "nospace")), any());
}
@Test
void shouldHandleIOException() {