karatelabs/karate · error · JsErrorException

Result of the Symbol.iterator method is not an object

Error message

Result of the Symbol.iterator method is not an object

What it means

Karate's JS engine evaluates spread/destructuring/for-of by calling the operand's Symbol.iterator method; the spec requires that call to return an object. The engine throws this TypeError when Symbol.iterator exists and is callable but returns a non-object (e.g. undefined, a number, or a primitive). It enforces ECMAScript GetIterator semantics on custom iterator implementations.

Solutions

  1. Make Symbol.iterator return an object — typically `{ next() { ... } }` or a generator function result
  2. Convert the method to a generator (`*[Symbol.iterator]() { ... }`) so the engine returns the iterator object automatically
  3. If the value may not be iterable, guard with a check before spreading or looping

Example fix

// before
const obj = { *[Symbol.iterator]() {} }; // fine, but:
const bad = { [Symbol.iterator]() { const it = makeIter(); } }; // no return
// after
const good = { [Symbol.iterator]() { return makeIter(); } };
Defensive patterns

Strategy: validation

Validate before calling

// JS
function isIterable(x) { return x != null && typeof x[Symbol.iterator] === 'function'; }
if (!isIterable(obj)) throw new TypeError('not iterable');

Type guard

function isIterableWithIterator(x) {
  const it = x != null && typeof x[Symbol.iterator] === 'function' ? x[Symbol.iterator]() : null;
  return it !== null && typeof it === 'object';
}

Try / catch

try { for (const v of obj) { /* ... */ } } catch (e) { if (String(e).includes('Symbol.iterator')) { /* fall back to non-iterable handling */ } }

Prevention

When it happens

Trigger: A user-defined JS object defines a callable Symbol.iterator whose body returns undefined, a primitive, or forgets a return statement, and the object is then spread (...obj), destructured, or used in for-of.

Common situations: Hand-written iterator classes where the factory function returns nothing; iterators written for another engine that rely on implicit returns; refactoring that turned `return { next() {...} }` into just building the object without returning it.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12). Data as JSON: /api/errors/342c41fe1d4dfbaf. Report an issue: GitHub.

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/js/Interpreter.java:2940

     * over the raw iterator record (iterator object + its methods invoked
     * with arguments), NOT the value-only JsIterator walk: sent values,
     * throw/return forwarding, and the delegate's final completion value all
     * survive. Each re-yield goes through the same activation handoff, so
     * the next resume kind comes from whatever the driver sends.
     */
    private static Object evalYieldStar(Object operand, CoreContext context, GeneratorActivation act) {
        // GetIterator: call operand[@@iterator]() with operand as `this`
        JsIterator probe = null; // only to reuse getIterator's TypeError wording on non-iterables
        Object iteratorFn = operand instanceof ObjectLike ol
                ? ol.getMember(IterUtils.SYMBOL_ITERATOR, ol, context) : null;
        ObjectLike iterObj;
        if (iteratorFn instanceof JsCallable callable && operand instanceof ObjectLike receiver) {
            Object iter = callWith(callable, receiver, context);
            if (context.isStopped()) {
                return Terms.UNDEFINED;
            }
            if (!(iter instanceof ObjectLike io)) {
                throw JsErrorException.typeError("Result of the Symbol.iterator method is not an object");
            }
            iterObj = io;
        } else {
            // strings / raw lists / Java iterables — wrap the engine iterator
            // in the standard iterator-object shape so one loop serves all
            probe = IterUtils.getIterator(operand, context);
            iterObj = IterUtils.toIteratorObject(probe);
        }
        GeneratorActivation.ResumeKind kind = GeneratorActivation.ResumeKind.NEXT;
        Object sent = Terms.UNDEFINED;
        while (true) {
            Object step;
            switch (kind) {
                case NEXT -> {
                    step = invokeIteratorMethod(iterObj, "next", sent, context, true);
                    if (context.isStopped()) {
                        return Terms.UNDEFINED;
                    }

View on GitHub (pinned to a22eb90246)