karatelabs/karate · error · ParserException

unexpected character '\u%04X' at

Error message

unexpected character '\u%04X' at %d:%d

What it means

When a character above ASCII 127 cannot start an identifier or operator, the lexer checks its Unicode category: format, control, line-separator, or paragraph-separator characters are explicitly rejected because Unicode ID_Start/ID_Continue rules make them invalid in source text. The error names the code point and lexer position. Other non-ASCII characters fall through as IDENT and fail later in the parser instead.

Solutions

  1. Delete and retype the region around the reported line:column to remove invisible characters.
  2. Strip format/control characters from the script before use (e.g. replace /[\u200B-\u200D\uFEFF\u00AD]/g in an editor or build step).
  3. Save the feature file as UTF-8 without BOM.
  4. Paste code through a plain-text editor to drop rich-text artifacts.

Example fix

// before (contains invisible U+200D between 'a' and 'r')
* def name = va‍rValue
// after (retyped clean)
* def name = varValue
Defensive patterns

Strategy: validation

Validate before calling

// strip invisible format/control characters before eval
var clean = script.replace(/[\u200B-\u200D\uFEFF\u00AD\u2028\u2029]/g, '');

Try / catch

try {
  def x = eval(script);
} catch (e) {
  if (e.message && e.message.indexOf('unexpected character') >= 0) {
    karate.log('invisible character detected, clean and retype: ' + e.message);
  }
  throw e;
}

Prevention

When it happens

Trigger: Invisible characters pasted into embedded JS: zero-width joiner (U+200D), BOM (U+FEFF) mid-expression, soft hyphen (U+00AD), or U+2028/U+2029 line separators inside a JS expression on one line.

Common situations: Copy-pasting code from rich text editors, Slack, PDFs, or web pages that insert zero-width or directional-format characters; hidden BOM after edits; JSON fixtures carrying control characters interpolated into scripts.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12). Data as JSON: /api/errors/de53e08b95e0e2de. Report an issue: GitHub.

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/parser/JsLexer.java:889

                }
                return match('=') ? STAR_EQ : STAR;

            case '%':
                return match('=') ? PERCENT_EQ : PERCENT;

            default:
                // A non-ASCII char in a category that can never be identifier
                // material or whitespace is a SyntaxError (§12.2) — e.g. U+180E
                // Mongolian vowel separator (Cf; whitespace in old Unicode).
                // Everything else keeps the lenient IDENT fallback: chars the
                // JDK's Unicode tables don't classify yet (newer-Unicode
                // ID_Start additions land as UNASSIGNED here) must still lex
                // as identifiers.
                if (c > 127) {
                    int type = Character.getType(c);
                    if (type == Character.FORMAT || type == Character.CONTROL
                            || type == Character.LINE_SEPARATOR || type == Character.PARAGRAPH_SEPARATOR) {
                        throw new ParserException(String.format(
                                "unexpected character '\\u%04X' at %d:%d", (int) c, tokenLine + 1, tokenCol + 1));
                    }
                }
                // Unknown character - return as IDENT (will likely cause parse error)
                return IDENT;
        }
    }

    /** StringValue of a string-literal interior: escape sequences decoded to
     *  their characters. Shared by runtime evaluation (Interpreter) and the
     *  parser's {@code __proto__}-key comparison (B.3.1 counts escaped
     *  spellings). */
    public static String unescapeStringLiteral(String s) {
        if (s.indexOf('\\') == -1) {
            return s; // no escapes, fast path
        }
        StringBuilder sb = new StringBuilder(s.length());
        for (int i = 0; i < s.length(); i++) {

View on GitHub (pinned to a22eb90246)