json-path/JsonPath · error · InvalidPathException

Expected '?' but found

Error message

Expected '?' but found 

What it means

InvalidPathException thrown by readPlaceholderToken when a token inside a filter's placeholder expression is not the literal '?'. Positional predicate placeholders must be exactly '?'; anything else (a name, number, or typo) is rejected because the compiler cannot map it to a predicate.

Source

Thrown at json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java:437

        int expressionEndIndex = path.nextIndexOf(expressionBeginIndex, CLOSE_SQUARE_BRACKET);

        if (expressionEndIndex == -1) {
            return false;
        }

        String expression = path.subSequence(expressionBeginIndex, expressionEndIndex).toString();

        String[] tokens = expression.split(",");

        if (filterStack.size() < tokens.length) {
            throw new InvalidPathException("Not enough predicates supplied for filter [" + expression + "] at position " + path.position());
        }

        Collection<Predicate> predicates = new ArrayList<Predicate>();
        for (String token : tokens) {
            token = token != null ? token.trim() : null;
            if (!"?".equals(token == null ? "" : token)) {
                throw new InvalidPathException("Expected '?' but found " + token);
            }
            predicates.add(filterStack.pop());
        }

        appender.appendPathToken(PathTokenFactory.createPredicatePathToken(predicates));

        path.setPosition(expressionEndIndex + 1);

        return path.currentIsTail() || readNextToken(appender);
    }

    //
    // [?(...)]
    //
    private boolean readFilterToken(PathTokenAppender appender) {
        if (!path.currentCharIs(OPEN_SQUARE_BRACKET) && !path.nextSignificantCharIs(BEGIN_FILTER)) {
            return false;
        }

View on GitHub (pinned to 62a4c9f0f6)

Solutions

  1. Replace the invalid token with a plain '?' for positional predicates
  2. Use inline filter syntax [?(@.price < 10)] instead of placeholders if you don't want external predicates
  3. Trim and inspect the filter expression — the offending token is printed in the message

Example fix

// before
JsonPath.read(json, "$..book[? @.x]")
// after
JsonPath.read(json, "$..book[?(@.x)]")
Defensive patterns

Strategy: validation

Validate before calling

static boolean validPlaceholderFilter(String filterExpr) {
    // filterExpr is the text inside [ ], e.g. "?" or "? , ?"
    if (filterExpr.trim().isEmpty()) return false;
    for (String tok : filterExpr.split(",")) {
        if (!"?".equals(tok.trim())) return false;
    }
    return true;
}

Try / catch

try {
    return JsonPath.read(json, path);
} catch (InvalidPathException e) {
    if (e.getMessage().startsWith("Expected '?'")) {
        throw new IllegalArgumentException("Bad placeholder token in path: " + path);
    }
    throw e;
}

Prevention

When it happens

Trigger: Compiling a path like "$..book[?x]" or "$[ ?isTrue ]" where the filter bracket contains a placeholder token that is not exactly '?'.

Common situations: Typos such as '??' fragments, whitespace-embedded tokens after splitting on commas, confusion with inline filter syntax [?(@.x == 1)] vs positional [?].

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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