didi/DoKit · error · IllegalArgumentException

Unexpected char %#04x at %d in header name: %s

Error message

Unexpected char %#04x at %d in header name: %s

What it means

Thrown by CommonHeaders.Builder.checkNameAndValue when a header NAME contains a control character (<= U+0020) or a non-ASCII character (>= U+007F) at position i. HTTP/1.x header names must be visible US-ASCII tokens, so okhttp-style validators reject anything outside that range.

Source

Thrown at Android/dokit/src/main/java/com/didichuxing/doraemonkit/kit/network/common/CommonHeaders.java:284

    /**
     * Set a field with the specified value. If the field is not found, it is added. If the field is
     * found, the existing values are replaced.
     */
    public Builder set(String name, String value) {
      checkNameAndValue(name, value);
      removeAll(name);
      addLenient(name, value);
      return this;
    }

    private void checkNameAndValue(String name, String value) {
      if (name == null) throw new NullPointerException("name == null");
      if (name.isEmpty()) throw new IllegalArgumentException("name is empty");
      for (int i = 0, length = name.length(); i < length; i++) {
        char c = name.charAt(i);
        if (c <= '\u0020' || c >= '\u007f') {
          throw new IllegalArgumentException(format(
              "Unexpected char %#04x at %d in header name: %s", (int) c, i, name));
        }
      }
      if (value == null) throw new NullPointerException("value == null");
      for (int i = 0, length = value.length(); i < length; i++) {
        char c = value.charAt(i);
        if ((c <= '\u001f' && c != '\t') || c >= '\u007f') {
          throw new IllegalArgumentException(format(
              "Unexpected char %#04x at %d in %s value: %s", (int) c, i, name, value));
        }
      }
    }

    /** Equivalent to {@code build().get(name)}, but potentially faster. */
    public String get(String name) {
      for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) {
        if (name.equalsIgnoreCase(namesAndValues.get(i))) {
          return namesAndValues.get(i + 1);

View on GitHub (pinned to 626827cddb)

Solutions

  1. Restrict header names to visible ASCII tokens: [!#$%&'*+-.^_`|~0-9A-Za-z]
  2. Sanitize before add: name.replaceAll("[^\\x21-\\x7e]", "") or reject and log
  3. If you need arbitrary data, put it in the header VALUE (which allows tabs and is more permissive) with an ASCII name, or URL-encode it

Example fix

// before
builder.add("X-Trace\tId", value); // tab in name

// after
builder.add("X-Trace-Id", value);
Defensive patterns

Strategy: validation

Validate before calling

static boolean isValidHeaderName(String name) {
  if (name == null || name.isEmpty()) return false;
  for (int i = 0; i < name.length(); i++) {
    char c = name.charAt(i);
    if (c <= ' ' || c >= '\u007f') return false;
  }
  return true;
}

Prevention

When it happens

Trigger: builder.add("X-Custom Header", v) (space in name), add("X\tId", v) (tab), or a name carrying an accented/CJK character, e.g. add("版本", v). The exception message interpolates the offending char's hex code and its index.

Common situations: Non-ASCII header names from localization mistakes (using Chinese keys as pseudo-headers); names assembled from unsanitized user input or file content containing tabs/newlines.

Related errors


AI-assisted analysis of didi/DoKit@626827cddb (2026-08-14). Data as JSON: /api/errors/4ca6d5b1770f6035. Report an issue: GitHub.