eclipse-vertx/vert.x · error · IllegalArgumentException
a header value must not end with '\r' or '\n':<seq>
Error message
a header value must not end with '\r' or '\n':<seq>
What it means
After scanning a header value, if the CR/LF state machine in validateSequenceHeaderValue does not end in the NO_CR_LF_STATE, the value terminates in a bare '\r' or '\n'. Such values allow HTTP response-splitting/request-smuggling, so Vert.x throws IllegalArgumentException.
Source
Thrown at vertx-core/src/main/java/io/vertx/core/http/impl/HttpUtils.java:720
}
}
private static final int HIGHEST_INVALID_VALUE_CHAR_MASK = ~0x1F;
private static final int NO_CR_LF_STATE = 0;
private static final int CR_STATE = 1;
private static final int LF_STATE = 2;
/**
* This method is taken as we need to validate the header value for the non-printable characters.
*/
private static void validateSequenceHeaderValue(CharSequence seq, int index) {
// we already expect the very-first character to be non-printable
int state = validateValueChar(seq, NO_CR_LF_STATE, seq.charAt(index));
for (int i = index + 1; i < seq.length(); i++) {
state = validateValueChar(seq, state, seq.charAt(i));
}
if (state != NO_CR_LF_STATE) {
throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq);
}
}
private static int validateValueChar(CharSequence seq, int state, char ch) {
/*
* State:
* 0: Previous character was neither CR nor LF
* 1: The previous character was CR
* 2: The previous character was LF
*/
if (ch == 0x7F) {
throw new IllegalArgumentException("a header value contains a prohibited character '127': " + seq);
}
if ((ch & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) {
// this is a rare scenario
validateNonPrintableCtrlChar(seq, ch);
// this can include LF and CR as they are non-printable characters
if (state == NO_CR_LF_STATE) {View on GitHub (pinned to fb308bd8c3)
Solutions
- Trim trailing CR/LF characters from the value before setting the header
- Join multi-line content with a single space or use a separator like ", " instead of \r\n
- Never pass untrusted, untrimmed input directly as a header value
Example fix
// before
String v = multiLineDescription; // ends with "\n"
request.putHeader("X-Desc", v);
// after
String v = multiLineDescription.replaceAll("[\\r\\n]+$", "").replace("\r\n", " ");
request.putHeader("X-Desc", v); Defensive patterns
Strategy: validation
Validate before calling
String stripTrailingCrLf(String v) {
int end = v.length();
while (end > 0 && (v.charAt(end - 1) == '\r' || v.charAt(end - 1) == '\n')) end--;
return v.substring(0, end);
}
// apply before headers.set/add Try / catch
try {
request.putHeader(name, value);
} catch (IllegalArgumentException e) {
request.putHeader(name, stripTrailingCrLf(value).replace("\r\n", " "));
} Prevention
- Trim trailing CR/LF from all header values, especially untrusted input
- Replace internal line breaks with spaces when flattening multi-line text
- Treat any header containing CR/LF as suspicious (request-smuggling defense)
When it happens
Trigger: Setting an HTTP header whose value ends with '\r' or '\n' (or a CR-LF combination) — detected by the final state check after validateValueChar walks the whole sequence.
Common situations: Joining multi-line config or log text into a single header; appending line separators to header values by mistake; untrusted input containing trailing newline kept untrimmed.
Related errors
- a header value contains a prohibited character '127': <value
- a header value contains a prohibited character '127': <seq>
- only '\n' is allowed after '\r': <seq>
- only ' ' and '\t' are allowed after '\n': <seq>
- a header value contains a prohibited character '<charCode>':
AI-assisted analysis of eclipse-vertx/vert.x@fb308bd8c3 (2026-09-06).
Data as JSON: /api/errors/bb418d408ac16ef3.
Report an issue: GitHub.