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

  1. Trim trailing CR/LF characters from the value before setting the header
  2. Join multi-line content with a single space or use a separator like ", " instead of \r\n
  3. 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

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


AI-assisted analysis of eclipse-vertx/vert.x@fb308bd8c3 (2026-09-06). Data as JSON: /api/errors/bb418d408ac16ef3. Report an issue: GitHub.