karatelabs/karate · error · io.karatelabs.js.EngineException

<cause message>

Error message

<cause message>

What it means

When the promise's underlying future completes with ExecutionException, join unwraps the cause and rethrows it: RuntimeExceptions directly, anything else as EngineException(cause.getMessage(), cause). The message "<cause message>" is therefore whatever the original failure produced — this error is a wrapper, and the root cause is the real diagnosis target.

Solutions

  1. Inspect the cause chain (getCause) — fix the root exception, not the wrapper.
  2. If the cause is a JS error, run the failing script standalone to see the original stack.
  3. Ensure async tasks catch and convert their failures into promise rejections with meaningful messages.
  4. Log the full EngineException with its cause rather than only the top-level message.

Example fix

// before
} catch (EngineException e) { log(e.getMessage()); } // loses root cause
// after
} catch (EngineException e) { log(e.getMessage(), e.getCause()); } // root cause visible
Defensive patterns

Strategy: try-catch

Try / catch

try { return p.join(); } catch (EngineException e) { Throwable root = e; while (root.getCause() != null) root = root.getCause(); log.error("promise failed, root cause: " + root, root); throw root instanceof RuntimeException re ? re : e; }

Prevention

When it happens

Trigger: The async computation behind the promise threw inside the executor thread: a JS runtime error during an awaited task, an exception in host callback code, or any checked exception wrapped by the future.

Common situations: JS script errors surfacing through await; NPEs or IO errors thrown by host-implemented async tasks; executor rejected/failed tasks; version changes that alter an underlying API the async task calls.

Related errors


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

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/js/JsPromise.java:248

    /** {@link #await()} with a deadline; throws {@link EngineTimeoutException}
     *  if the promise has not settled by then. */
    public Object join(Duration timeout) {
        try {
            return timeout == null
                    ? toFuture().get()
                    : toFuture().get(Math.max(timeout.toMillis(), 0), TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new EngineInterruptedException();
        } catch (TimeoutException e) {
            throw new EngineTimeoutException("timed out waiting for promise");
        } catch (ExecutionException e) {
            Throwable cause = e.getCause() == null ? e : e.getCause();
            if (cause instanceof RuntimeException re) {
                throw re;
            }
            throw new EngineException(cause.getMessage(), cause);
        }
    }

    /**
     * The reverse association that makes a round trip lossless: handing a
     * promise's own {@link #toFuture()} back into JS recovers the original
     * {@code JsPromise}, independent of the scope's stage cache.
     */
    static final class PromiseView extends CompletableFuture<Object> {

        final JsPromise owner;

        PromiseView(JsPromise owner) {
            this.owner = owner;
        }
    }

    @Override

View on GitHub (pinned to a22eb90246)