Component
org.springframework.ai.converter.MarkdownCodeBlockCleaner
File: spring-ai-model/src/main/java/org/springframework/ai/converter/MarkdownCodeBlockCleaner.java
Spring AI version
Checked against main (commit fb51ef2, as of 2026-07-17).
Describe the bug
clean(String) does not guard against negative lengths when stripping the
trailing fence on a single-line input. For input strings that are only
backticks (3 to 5 of them), text.startsWith("```") and text.endsWith("```")
are both true simultaneously (the fence characters overlap), so the method
proceeds into the single-line branch and eventually calls:
text = text.substring(0, text.length() - 3);
When text.length() - 3 is negative, this throws
StringIndexOutOfBoundsException instead of returning something sane (e.g.
the original text, or an empty string).
Steps to reproduce
MarkdownCodeBlockCleaner cleaner = new MarkdownCodeBlockCleaner();
cleaner.clean("```");
cleaner.clean("````");
cleaner.clean("`````");
cleaner.clean("``````");
Verified output (JDK 21.0.11, logic extracted verbatim from clean()):
INPUT: ``` -> THROWS: java.lang.StringIndexOutOfBoundsException: Range [0, -3) out of bounds for length 0
INPUT: ```` -> THROWS: java.lang.StringIndexOutOfBoundsException: Range [0, -2) out of bounds for length 1
INPUT: ````` -> THROWS: java.lang.StringIndexOutOfBoundsException: Range [0, -1) out of bounds for length 2
INPUT: `````` -> OUTPUT: [] (does not throw)
Expected behavior
The cleaner should handle degenerate/malformed fence-only input gracefully
(e.g. return the input unchanged, or an empty string) rather than throwing an
unchecked exception. Since clean() is typically invoked on raw LLM output,
a malformed or truncated response from a model could trigger this at
runtime in application code that doesn't expect it.
Actual behavior
StringIndexOutOfBoundsException is thrown for 3-, 4-, and 5-backtick-only
inputs.
Additional context
I extracted the clean() logic verbatim from the current main branch source
and ran it standalone under a real JDK 21 to confirm the above output — this
isn't a theoretical trace, it's actual reproduced behavior. I have not yet
run it through the module's own Maven build/test suite (just an isolated
JVM run of the identical logic), so I'm happy to be corrected if there's
context I'm missing, or if this overlaps with prior triage.
I'd like to submit a fix + accompanying unit tests for this if it's
confirmed and not already tracked elsewhere. Let me know if this overlaps
with prior work (e.g. #6403) or if there's a preferred approach (early
return vs. defensive length check).
Component
org.springframework.ai.converter.MarkdownCodeBlockCleanerFile:
spring-ai-model/src/main/java/org/springframework/ai/converter/MarkdownCodeBlockCleaner.javaSpring AI version
Checked against
main(commitfb51ef2, as of 2026-07-17).Describe the bug
clean(String)does not guard against negative lengths when stripping thetrailing fence on a single-line input. For input strings that are only
backticks (3 to 5 of them),
text.startsWith("```")andtext.endsWith("```")are both
truesimultaneously (the fence characters overlap), so the methodproceeds into the single-line branch and eventually calls:
When
text.length() - 3is negative, this throwsStringIndexOutOfBoundsExceptioninstead of returning something sane (e.g.the original text, or an empty string).
Steps to reproduce
Verified output (JDK 21.0.11, logic extracted verbatim from
clean()):Expected behavior
The cleaner should handle degenerate/malformed fence-only input gracefully
(e.g. return the input unchanged, or an empty string) rather than throwing an
unchecked exception. Since
clean()is typically invoked on raw LLM output,a malformed or truncated response from a model could trigger this at
runtime in application code that doesn't expect it.
Actual behavior
StringIndexOutOfBoundsExceptionis thrown for 3-, 4-, and 5-backtick-onlyinputs.
Additional context
I extracted the
clean()logic verbatim from the currentmainbranch sourceand ran it standalone under a real JDK 21 to confirm the above output — this
isn't a theoretical trace, it's actual reproduced behavior. I have not yet
run it through the module's own Maven build/test suite (just an isolated
JVM run of the identical logic), so I'm happy to be corrected if there's
context I'm missing, or if this overlaps with prior triage.
I'd like to submit a fix + accompanying unit tests for this if it's
confirmed and not already tracked elsewhere. Let me know if this overlaps
with prior work (e.g. #6403) or if there's a preferred approach (early
return vs. defensive length check).