elastic/elasticsearch · error · IllegalArgumentException

Expected text at {} but found {}

Error message

Expected text at {} but found {}

What it means

throwOnNoText is called when the caller asked for text (e.g. text(), optimizedText(), or charBuffer()) but the current token is not a value token that carries text - for example the parser is positioned on START_OBJECT, START_ARRAY, FIELD_NAME, or END_*. The exception names the token location and the offending currentToken().

Source

Thrown at libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/json/JsonXContentParser.java:165

    public XContentString optimizedText() throws IOException {
        if (currentToken().isValue() == false) {
            throwOnNoText();
        }
        var parser = this.parser;
        if (parser instanceof FilteringParserDelegate delegate) {
            parser = delegate.delegate();
        }
        if (parser instanceof OptimizedTextCapable optimizedTextCapableParser) {
            var bytesRef = optimizedTextCapableParser.getValueAsText();
            if (bytesRef != null) {
                return bytesRef;
            }
        }
        return new Text(text());
    }

    private void throwOnNoText() {
        throw new IllegalArgumentException("Expected text at " + getTokenLocation() + " but found " + currentToken());
    }

    @Override
    public CharBuffer charBuffer() throws IOException {
        try {
            return CharBuffer.wrap(parser.getTextCharacters(), parser.getTextOffset(), parser.getTextLength());
        } catch (IOException e) {
            throw handleParserException(e);
        }
    }

    @Override
    public Object objectText() throws IOException {
        JsonToken currentToken = parser.getCurrentToken();
        if (currentToken == JsonToken.VALUE_STRING) {
            return text();
        } else if (currentToken == JsonToken.VALUE_NUMBER_INT || currentToken == JsonToken.VALUE_NUMBER_FLOAT) {
            return parser.getNumberValue();

View on GitHub (pinned to db6a809a66)

Solutions

  1. Always advance the parser to a value token (via nextToken()) before calling text()
  2. Switch on currentToken() and only call text() for VALUE_* tokens
  3. Use nextFieldName()/nextString() helpers that handle positioning for you

Example fix

// before
parser.nextToken(); // lands on FIELD_NAME "name"
String v = parser.text(); // throws - FIELD_NAME is not a value
// after
parser.nextToken(); // FIELD_NAME "name"
parser.nextToken(); // VALUE_STRING
String v = parser.text();
Defensive patterns

Strategy: type-guard

Validate before calling

XContentParser.Token t = parser.currentToken();
if (t == null || !t.isValue()) {
    throw new IllegalStateException("expected value token, got " + t);
}
String v = parser.text();

Type guard

static boolean isValueToken(XContentParser.Token t) {
    return t != null && t.isValue();
}

Try / catch

try { parser.text(); }
catch (IllegalArgumentException e) { /* advance token first */ }

Prevention

When it happens

Trigger: Calling a text-expecting method on JsonXContentParser when currentToken() is not a scalar value token (VALUE_STRING, VALUE_NUMBER, VALUE_TRUE, etc.). The optimized-text path also falls back to text() and calls throwOnNoText if the value-as-text lookup returns null.

Common situations: Forgetting to call nextToken() before reading text; reading text() at FIELD_NAME position expecting the field's value; consuming a token type the caller did not check; an off-by-one in a manual token loop.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/5b4e8378580d3f2e. Report an issue: GitHub.