karatelabs/karate · error · java.util.NoSuchElementException

NoSuchElementException

Error message

NoSuchElementException

What it means

JsObject's property iterator throws NoSuchElementException when next() is called after all own properties have been yielded (peeked exhausted and advance() false). Properties are read lazily at yield time so callback-driven mutations propagate, which makes over-advancing this iterator especially easy to trigger from user callbacks.

Solutions

  1. Guard next() with hasNext() in Java; in JS rely on the done flag.
  2. Avoid add/delete of properties during enumeration, or iterate over a snapshot of keys.
  3. Do not re-enter or share the iterator from inside the iteration callback.
  4. Upgrade karate-js if triggered by library-internal enumeration.

Example fix

// before
while (true) { KeyValue kv = it.next(); ... }
// after
while (it.hasNext()) { KeyValue kv = it.next(); ... }
Defensive patterns

Strategy: try-catch

Validate before calling

if (obj == null) return; // no properties to enumerate

Type guard

boolean canAdvance(Iterator<?> it) { return it != null && it.hasNext(); }

Try / catch

try { while (it.hasNext()) { KeyValue kv = it.next(); } } catch (NoSuchElementException e) { log.warn("property iterator over-advanced", e); }

Prevention

When it happens

Trigger: Advancing the object property iterator past the last key; an enumeration loop (for...in, Object.entries internals) whose callback deletes/adds properties or re-enters the iterator.

Common situations: JS callbacks that mutate the object being enumerated; deep-equal/clone helpers iterating properties; host Java code consuming the iterator without hasNext().

Related errors


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

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/js/JsObject.java:1096

                    // {entries,values}/getter-making-future-key-nonenumerable).
                    if (s.tombstoned) continue;
                    if (!isEnumerable(s.name)) continue;
                    peeked = s;
                    return true;
                }
                peeked = null;
                return false;
            }

            @Override
            public boolean hasNext() {
                return peeked != null || advance();
            }

            @Override
            public KeyValue next() {
                if (peeked == null && !advance()) {
                    throw new NoSuchElementException();
                }
                PropertySlot s = peeked;
                peeked = null;
                // Read at yield time so callback-driven mutations before
                // the next next() call propagate. With ctx, accessor slots
                // resolve via getter invocation; without ctx, they surface
                // as null (Java-interop semantic).
                Object v = ctx != null
                        ? s.read(JsObject.this, ctx)
                        : s instanceof DataSlot ds ? ds.value : null;
                return new KeyValue(JsObject.this, index++, s.name, v);
            }
        };
    }

    /** Non-tombstoned slots in §9.1.11.1 ordering — integer-index slots
     *  ascending, then named slots in insertion order. Backs
     *  {@link #jsEntries(CoreContext)}; the enumerable filter is applied at

View on GitHub (pinned to a22eb90246)