Netflix/zuul · error · ZuulException

Invalid header field: char

Error message

Invalid header field: char ${(int) value.charAt(pos)} in string ${value} does not comply with RFC 7230

What it means

Zuul validates every HTTP header value against RFC 7230 field-content rules before storing it in a Headers object. If a value contains a forbidden character (e.g. CTL characters, bare newline/carriage-return outside obs-fold handling, or other non-visible ASCII), Headers increments an invalidHeaderCounter metric and throws this ZuulException. This is a security/robustness guard against header-injection and malformed header propagation.

Solutions

  1. Sanitize the header value before adding it: strip or percent-encode control/non-ASCII characters (only visible ASCII + SP/HT allowed per RFC 7230).
  2. Replace line breaks and invalid characters, e.g. value.replaceAll("[\\r\\n\\t\\x00-\\x1f\\x7f]", "") before headers.add(...).
  3. Trace which header/value is invalid from the message (it includes the offending char code and full string) and fix the producer of that value.
  4. If the value legitimately needs non-ASCII, encode it (e.g. RFC 5987/2047 encoding) rather than passing raw bytes.
  5. For responses from origins, validate/normalize headers before copying them into the Zuul Headers object.

Example fix

// before
String traceId = request.getParameter("traceId");
headers.add("X-Trace-Id", traceId);
// after
String traceId = request.getParameter("traceId").replaceAll("[^\\x20-\\x7E]", "");
headers.add("X-Trace-Id", traceId);
Defensive patterns

Strategy: validation

Validate before calling

// validate/sanitize header values before adding to Zuul Headers
private static final Pattern VALID_HEADER = Pattern.compile("^[\\x20-\\x7E]+$");
static String safeHeader(String value) {
    if (value == null || !VALID_HEADER.matcher(value).matches()) {
        throw new IllegalArgumentException("Invalid header value");
    }
    return value;
}
headers.add("X-Custom", safeHeader(userInput));

Type guard

boolean isRfc7230Header(String value) {
    return value != null && value.chars().allMatch(c -> c == '\t' || (c >= 0x20 && c <= 0x7E));
}

Try / catch

try {
    headers.add("X-Trace-Id", userInput);
} catch (ZuulException e) {
    if (e.getMessage().startsWith("Invalid header field")) {
        log.warn("Dropping invalid header value: {}", e.getMessage());
    }
}

Prevention

When it happens

Trigger: Calling any Headers add/set/put/constructor path (all route through validateField) with a header value containing a character rejected by findInvalid — commonly \r, \n, or non-ASCII/control characters — e.g. building a request header from untrusted user input or a downstream service echo.

Common situations: User-supplied input copied verbatim into a proxied header (X-Forwarded-*, custom tracing headers); log injection attempts with CRLF; legacy systems emitting Latin-1/UTF-8 bytes in header values; faulty upstream responses echoing header values with embedded newlines.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.


AI-assisted analysis of Netflix/zuul@14bf53c52d (2026-09-07). Data as JSON: /api/errors/ebccbcbde82f1466. Report an issue: GitHub.

Appendix: source

Thrown at zuul-core/src/main/java/com/netflix/zuul/message/Headers.java:785

     */
    private static boolean isValid(@Nullable String value) {
        if (value == null || findInvalid(value) == ABSENT) {
            return true;
        }
        invalidHeaderCounter.increment();
        return false;
    }

    /**
     * Checks if the input value is compliant with our RFC 7230 based check
     * Returns input value if valid, raises ZuulException otherwise
     */
    private static String validateField(@Nullable String value) {
        if (value != null) {
            int pos = findInvalid(value);
            if (pos != ABSENT) {
                invalidHeaderCounter.increment();
                throw new ZuulException("Invalid header field: char " + (int) value.charAt(pos) + " in string " + value
                        + " does not comply with RFC 7230");
            }
        }
        return value;
    }

    /**
     * Validated the input value based on RFC 7230 but more lenient.
     * Currently, only ASCII control characters are considered invalid.
     *
     * Returns the index of first invalid character. Returns {@link #ABSENT} if absent.
     */
    private static int findInvalid(String value) {
        for (int i = 0; i < value.length(); i++) {
            char c = value.charAt(i);
            // ASCII non-control characters, per RFC 7230 but slightly more lenient
            if (c < 31 || c == 127) {
                return i;

View on GitHub (pinned to 14bf53c52d)