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
- Sanitize the header value before adding it: strip or percent-encode control/non-ASCII characters (only visible ASCII + SP/HT allowed per RFC 7230).
- Replace line breaks and invalid characters, e.g. value.replaceAll("[\\r\\n\\t\\x00-\\x1f\\x7f]", "") before headers.add(...).
- 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.
- If the value legitimately needs non-ASCII, encode it (e.g. RFC 5987/2047 encoding) rather than passing raw bytes.
- 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
- Strip CR/LF and control characters from any user-supplied data before putting it in headers
- Only allow visible ASCII (0x20-0x7E) plus HTAB in header values
- Encode non-ASCII content (RFC 5987/2047) instead of passing raw bytes
- Validate headers copied from untrusted origins before adding them to Zuul Headers
- Watch the invalidHeaderCounter metric for spikes indicating injection attempts
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)