json-path/JsonPath · error · InvalidPathException

Unexpected character: %c

Error message

Unexpected character: %c

What it means

This InvalidPathException is thrown by JsonPath's FilterCompiler while parsing an inline filter predicate. Inside readValueNode, when the current token is the NOT operator ('!'), the compiler expects a path starting with the document context char '@' or the evaluation context char '#' to follow; any other character (e.g. '!x' or '! 'a' == 'b') is unrecoverable. It means the negation operator in the filter is not immediately followed by a valid path operand.

Source

Thrown at json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java:102

             return result;
        } catch (InvalidPathException e){
            throw e;
        } catch (Exception e) {
            throw new InvalidPathException("Failed to parse filter: " + filter + ", error on position: " + filter.position() + ", char: " + filter.currentChar());
        }
    }

    private ValueNode readValueNode() {
        switch (filter.skipBlanks().currentChar()) {
            case DOC_CONTEXT  : return readPath();
            case EVAL_CONTEXT : return readPath();
            case NOT:
                filter.incrementPosition(1);
                switch (filter.skipBlanks().currentChar()) {
                    case DOC_CONTEXT  : return readPath();
                    case EVAL_CONTEXT : return readPath();
                    default: throw new InvalidPathException(String.format("Unexpected character: %c", NOT));
                }
            default : return readLiteral();
        }
    }

    private ValueNode readLiteral(){
        switch (filter.skipBlanks().currentChar()){
            case SINGLE_QUOTE:  return readStringLiteral(SINGLE_QUOTE);
            case DOUBLE_QUOTE: return readStringLiteral(DOUBLE_QUOTE);
            case TRUE:  return readBooleanLiteral();
            case FALSE: return readBooleanLiteral();
            case MINUS: return readNumberLiteral();
            case NULL:  return readNullLiteral();
            case OPEN_OBJECT: return readJsonLiteral();
            case OPEN_ARRAY: return readJsonLiteral();
            case PATTERN: return readPattern();
            default:    return readNumberLiteral();
        }

View on GitHub (pinned to 62a4c9f0f6)

Solutions

  1. Fix the filter so '!' is directly followed by a path operand: use "$[?(!@.field)]" or "$[?(!(@.field == 'x'))]"
  2. If you meant inequality, use the '!=' operator instead of '!': "$[?(@.field != 'x')]"
  3. Wrap negation in parentheses around a comparison: "$[?(!(@.a && @.b))]" rather than negating a literal
  4. Verify the whole expression compiles with Filter.compile(predicate) in a unit test before deploying

Example fix

// before
String path = "$[?(! 'active' == true)]";
// after
String path = "$[?(!@.active == true)]";
Defensive patterns

Strategy: validation

Validate before calling

// Validate a negation before compiling
boolean validNegation(String predicate) {
    int i = predicate.indexOf('!');
    if (i < 0 || i == predicate.length() - 1) return false;
    char next = predicate.charAt(i + 1);
    return next == '@' || next == '#' || next == '(';
}
// usage: if (!validNegation("$[?(!@.x)]")) throw new IllegalArgumentException("bad filter");

Type guard

static boolean isNegatingPath(String filter) {
    java.util.regex.Matcher m = java.util.regex.Pattern.compile("!\\s*[@#(]").matcher(filter);
    return m.find();
}

Try / catch

try {
    Object result = JsonPath.parse(json).read(path);
} catch (InvalidPathException e) {
    log.error("Malformed filter predicate, ! must precede @ or # : " + path, e);
    throw new IllegalArgumentException("Invalid JSONPath filter: " + path, e);
}

Prevention

When it happens

Trigger: Compiling a filter path where '!' is followed by something that is not '@' or '#', e.g. "$[?(! foo)]", "$[?(! 'a' == 'b')]", or "$[?(!)]" — anywhere Filter.compile / JsonPath.parse(...).read(path) evaluates the predicate.

Common situations: Hand-written filter expressions with a stray space or wrong operand after '!' (users expect '!= value' syntax to be written as '! ='); macro-generated paths that prepend '!' to a literal instead of a path; upgrading JsonPath and an expression that previously slipped through stricter validation now fails.

Related errors


AI-assisted analysis of json-path/JsonPath@62a4c9f0f6 (2026-09-11). Data as JSON: /api/errors/3f2ee6b405c2f56d. Report an issue: GitHub.