What
Reported upstream as palantir/palantir-java-format#1680 (open since 2026-05-29, no comments). It reproduces here on 2.98.0.2. Minimal case:
class C {
void m(java.sql.Connection con) throws Exception {
con.createStatement()
.execute("ALTER DATABASE tempdb MODIFY FILE (NAME = 'tempdev', FILENAME = 'D:\\tempDb\\DATA\\tempdb.mdf')");
}
}
open-java-format C.java exits 2 with C.java:5:26: error: unclosed string literal, while javac compiles the same file. Replace the escaped backslashes with forward slashes and the file formats.
In the upstream report the errors point at methods further down the file, which makes the cause hard to see: the line and column come from the rewritten source the formatter parses to check itself, not from the file on disk.
Why it happens
StringWrapper.stringComponents splits the literal's source text at whitespace and at escaped whitespace, and hasEscapedWhitespaceAt/hasEscapedNewlineAt look for \t, \n and \r without asking whether that backslash is itself escaped. In DATA\\tempDb the second backslash and the t look like an escaped tab, so the split lands inside the escaped backslash. StringWrapper.getReflowReplacements on the file above returns:
"ALTER DATABASE tempdb MODIFY FILE (NAME = 'tempdev', FILENAME = 'D:\\tempDb\\DATA\"
+ "\tempdb.mdf')"
The first piece ends in a lone backslash, which escapes the closing quote — that is the error. The second piece starts with \t, a tab, so even if it parsed the string would no longer be the one the author wrote.
\n and \r are mis-read the same way, but that branch advances past the escape, so the split is merely in an odd place and the result still parses and still means the same: the same file with D:\\nempDb formats fine. Only \\t produces code that does not parse.
Fix
Walk the literal left to right consuming escape sequences, so that \\ takes two characters and the t after it is an ordinary letter.
Mind the output promise: files whose long strings contain \\n or \\r format today, and the fix moves where they are split, so their output changes. Measure that over a corpus and decide which release it belongs to, as in #22 and #31.
Done when
- the file above formats, and formatting it twice is a no-op;
- a golden covers a wrapped literal with
\\t, \\n, \\r, \\\\ and \u escapes, and fails without the fix;
- a test asserts the string's value is unchanged by wrapping, not only that the result parses;
- a corpus run says how many files the
\\n/\\r split moves.
What
Reported upstream as palantir/palantir-java-format#1680 (open since 2026-05-29, no comments). It reproduces here on 2.98.0.2. Minimal case:
open-java-format C.javaexits 2 withC.java:5:26: error: unclosed string literal, whilejavaccompiles the same file. Replace the escaped backslashes with forward slashes and the file formats.In the upstream report the errors point at methods further down the file, which makes the cause hard to see: the line and column come from the rewritten source the formatter parses to check itself, not from the file on disk.
Why it happens
StringWrapper.stringComponentssplits the literal's source text at whitespace and at escaped whitespace, andhasEscapedWhitespaceAt/hasEscapedNewlineAtlook for\t,\nand\rwithout asking whether that backslash is itself escaped. InDATA\\tempDbthe second backslash and thetlook like an escaped tab, so the split lands inside the escaped backslash.StringWrapper.getReflowReplacementson the file above returns:The first piece ends in a lone backslash, which escapes the closing quote — that is the error. The second piece starts with
\t, a tab, so even if it parsed the string would no longer be the one the author wrote.\nand\rare mis-read the same way, but that branch advances past the escape, so the split is merely in an odd place and the result still parses and still means the same: the same file withD:\\nempDbformats fine. Only\\tproduces code that does not parse.Fix
Walk the literal left to right consuming escape sequences, so that
\\takes two characters and thetafter it is an ordinary letter.Mind the output promise: files whose long strings contain
\\nor\\rformat today, and the fix moves where they are split, so their output changes. Measure that over a corpus and decide which release it belongs to, as in #22 and #31.Done when
\\t,\\n,\\r,\\\\and\uescapes, and fails without the fix;\\n/\\rsplit moves.