elastic/elasticsearch · error · IllegalArgumentException

Illegal list shortcut value [name].

Error message

Illegal list shortcut value [name].

What it means

Def.lookupSetter handles dynamic field writes on 'def'. Symmetric to the getter, for a List receiver the name must be an integer index (mylist.0 = value). Integer.parseInt(name) throws NumberFormatException for non-numeric names, caught and rethrown as 'Illegal list shortcut value [name]'. This is the store (assignment) variant.

Source

Thrown at modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java:710

        if (setter != null) {
            return setter;
        }

        // special case: maps, and lists
        if (Map.class.isAssignableFrom(receiverClass)) {
            // maps allow access like mymap.key
            // wire 'key' as a parameter, its a constant in painless
            return MethodHandles.insertArguments(MAP_PUT, 1, name);
        } else if (List.class.isAssignableFrom(receiverClass)) {
            // lists allow access like mylist.0
            // wire '0' (index) as a parameter, its a constant. this also avoids
            // parsing the same integer millions of times!
            try {
                int index = Integer.parseInt(name);
                return MethodHandles.insertArguments(LIST_SET, 1, index);
            } catch (final NumberFormatException exception) {
                throw new IllegalArgumentException("Illegal list shortcut value [" + name + "].");
            }
        }

        throw new IllegalArgumentException("dynamic setter [" + typeToCanonicalTypeName(receiverClass) + ", " + name + "] not found");
    }

    /**
     * Returns a method handle to normalize the index into an array. This is what makes lists and arrays stored in {@code def} support
     * negative offsets.
     * @param receiverClass Class of the array to store the value in
     * @return a MethodHandle that accepts the receiver as first argument, the index as second argument, and returns the normalized index
     *   to use with array loads and array stores
     */
    static MethodHandle lookupIndexNormalize(Class<?> receiverClass) {
        if (receiverClass.isArray()) {
            return ArrayIndexNormalizeHelper.arrayIndexNormalizer(receiverClass);
        } else if (Map.class.isAssignableFrom(receiverClass)) {
            // noop so that mymap[key] doesn't do funny things with negative keys

View on GitHub (pinned to db6a809a66)

Solutions

  1. Use an integer index for List assignment: mylist[0] = value (bracket form preferred).
  2. If named-key assignment is intended, ensure the receiver is a Map.
  3. Use bracket syntax mylist[i] = v for dynamic or computed indices.

Example fix

// before
def x = [1, 2, 3];
x.first = 99;
// after
x[0] = 99;
Defensive patterns

Strategy: type-guard

Type guard

// Guard List dot-set: only integer names
function safeListSet(list, name, value) {
  if (!/^-?\d+$/.test(String(name))) throw new Error(`Use integer index for List assignment; got '${name}'`);
  list[Number(name)] = value;
}

Try / catch

// Surface 'Illegal list shortcut value' on the store path; advise bracket assignment.

Prevention

When it happens

Trigger: A Painless script assigns via dot-shortcut on a List with a non-integer name: def x = [1,2,3]; x.first = 9. Maps allow mymap.key = v, but Lists require integer indices.

Common situations: Confusing List and Map semantics. Expecting named-element assignment on a sequence. Runtime type being List when Map was assumed.

Related errors


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