karatelabs/karate · error · JsErrorException

Class constructor cannot be invoked without 'new

Error message

Class constructor ${n}cannot be invoked without 'new'

What it means

Calling a class constructor as a plain function (no `new`) is a TypeError per ECMAScript; invokeCallable raises this when the callee is a JsFunctionNode whose isClassConstructor is true and newKeyword is false. The message includes the class name (with a trailing space when named).

Solutions

  1. Add `new` at the call site: `new MyClass(...)`
  2. If the call site is generic, branch on whether the value is a class before invoking
  3. Keep ES5 function-constructors as plain functions if they must be callable without new
  4. In dynamic/reflective host code, use the construct path rather than the plain-call path for classes

Example fix

// before
const c = MyClass(1, 2); // throws
// after
const c = new MyClass(1, 2);
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof MyClass !== 'function' || MyClass.toString().trim().startsWith('class')) { /* must use new */ }

Type guard

function isClassCtor(f) { return typeof f === 'function' && /^\s*class\s/.test(String(f)); }

Try / catch

try { const c = MyClass(); } catch (e) { if (String(e).includes("cannot be invoked without 'new'")) { c = new MyClass(); } else { throw e; } }

Prevention

When it happens

Trigger: `MyClass()` instead of `new MyClass()`; calling a class constructor via call/apply-style plain invocation; super-adjacent mistakes where a helper invokes the constructor directly; evaluating `typeof`-style debug calls that invoke the class.

Common situations: Migrating ES5 function-constructors to class syntax while old call sites (without new) remain; dynamic dispatch tables storing classes and invoking them without new; minified/generated code losing the new.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

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

    private static Object evalOptionalCall(Node node, CoreContext context) {
        Object o = PropertyAccess.getCallable(node.getFirst(), context);
        Object receiver = context.callReceiver; // consume immediately (see field contract)
        if (o == PropertyAccess.SHORT_CIRCUITED) return PropertyAccess.SHORT_CIRCUITED;
        if (o == null || o == Terms.UNDEFINED) return PropertyAccess.SHORT_CIRCUITED;
        Node callExpr = node.get(1); // FN_CALL_EXPR -> [?., (, FN_CALL_ARGS, )]
        Node fnArgsNode = callExpr.get(2);
        return invokeCallable(o, receiver, fnArgsNode, false, node.getFirst(), context);
    }

    private static Object invokeCallable(Object o, Object receiver, Node fnArgsNode,
                                          boolean newKeyword, Node node, CoreContext context) {
        if (o instanceof JsCallable callable) {
            if (newKeyword && !callable.isConstructable()) {
                throw JsErrorException.typeError(node.getTextIncludingWhitespace() + " is not a constructor");
            }
            if (!newKeyword && callable instanceof JsFunctionNode cf && cf.isClassConstructor) {
                String n = cf.name == null || cf.name.isEmpty() ? "" : cf.name + " ";
                throw JsErrorException.typeError("Class constructor " + n + "cannot be invoked without 'new'");
            }
            Object[] args = evalCallArgs(fnArgsNode, context);
            // An argument that threw means there is no call to make. evalSuperCall already does
            // this; every other call site did not, so `f(mustSucceed())` invoked f anyway.
            if (context.isError()) {
                return Terms.UNDEFINED;
            }
            // Convert JS types to Java types if JS/Java boundary:
            // - undefined → null
            // - JsValue (JsDate, etc.) → unwrapped via getJavaValue()
            if (callable.isExternal()) {
                for (int i = 0; i < args.length; i++) {
                    Object arg = args[i];
                    if (arg == Terms.UNDEFINED) {
                        args[i] = null;
                    } else if (arg instanceof JsValue jv && !(arg instanceof JsPrimitive)) {
                        // Unwrap JsValue (JsDate, JsUint8Array) but not JsPrimitive (Boolean/String/Number constructors)
                        args[i] = jv.getJavaValue();

View on GitHub (pinned to a22eb90246)