karatelabs/karate · error · ParserException

unexpected character '#' at

Error message

unexpected character '#' at %d:%d

What it means

Karate's JS lexer uses `#` only as the start of a private class field/method name (`#name`), which must be followed by a valid identifier start character. A `#` appearing anywhere else — with no identifier, or after a character that cannot start one — is not part of the JS grammar and is rejected at tokenization time.

Solutions

  1. Quote the `#` text as a string: `'#'` or `'http://x#frag'`.
  2. Replace `#` comments with `//` in JS code.
  3. If a private field was intended, ensure it appears in a class body and `#` is immediately followed by a valid identifier (`#count`).
  4. Remove the stray `#` if it is a leftover from Karate placeholder syntax used in the wrong position.

Example fix

// before
* def x = #
// after
* def x = '#'
Defensive patterns

Strategy: validation

Validate before calling

// warn about bare # outside strings/class bodies before eval
if (/(^|[^\w'"#/])#(?!\p{ID_Start})/u.test(script)) console.warn('stray # in JS: ' + script);

Try / catch

try {
  def x = eval(script);
} catch (e) {
  if (e.message && e.message.indexOf("unexpected character '#'") >= 0) {
    karate.log('# must be quoted or start a private name at ' + e.message);
  }
  throw e;
}

Prevention

When it happens

Trigger: A bare `#` or `# ` or `#+` in embedded JS (e.g. `* def x = #`); a `#` used as a comment marker (JS comments are `//` or `/* */`, not `#`); `#` intended as a string left unquoted; a private name like `#1x` starting with a non-identifier character.

Common situations: Coming from shell/Python habits and using `#` for comments inside JS blocks; placing Karate-style `#string` placeholder markers inside actual JS expression context where they are not valid; pasting URLs (`https://...#frag`) unquoted; private-field syntax `#x` used outside a class body.

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/5028259bc5810d99. Report an issue: GitHub.

Appendix: source

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

            }
        }
        return keywordOrIdent(tokenStart, pos - tokenStart);
    }

    // `#` + IdentifierName, one token including the hash. A bare `#` has no other
    // role in the grammar, so anything else is a hard error rather than the lenient
    // IDENT fallback — a half-lexed `#x` would parse into silently wrong garbage.
    private TokenType scanPrivateName() {
        advance();
        if (pos < length) {
            char c = source.charAt(pos);
            if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$'
                    || (c > 127 && Character.isJavaIdentifierStart(c))) {
                scanIdentifier();
                return PRIVATE_NAME;
            }
        }
        throw new ParserException(String.format(
                "unexpected character '#' at %d:%d", tokenLine + 1, tokenCol + 1));
    }

    private TokenType keywordOrIdent(int start, int len) {
        // Note: "this" is NOT a keyword in the lexer - it's an identifier that gets
        // special handling in the parser/evaluator. "void" lexes as the VOID keyword
        // so the parser can dispatch it as a unary operator.
        // Fast path: switch on length first, then first char
        if (len == 2) {
            char c0 = source.charAt(start);
            if (c0 == 'i') {
                if (source.charAt(start + 1) == 'f') return IF;
                if (source.charAt(start + 1) == 'n') return IN;
            } else if (c0 == 'o' && source.charAt(start + 1) == 'f') {
                return OF;
            } else if (c0 == 'd' && source.charAt(start + 1) == 'o') {
                return DO;
            }

View on GitHub (pinned to a22eb90246)