karatelabs/karate · error · JsErrorException (typeError)

Cannot convert object to primitive value

Error message

Cannot convert object to primitive value

What it means

If an object's @@toPrimitive method returns an object (not a primitive), the spec says ToPrimitive fails with a TypeError. Terms.java enforces this: only null, undefined, primitives, or BigInt results are accepted from the exotic conversion path.

Solutions

  1. Return a primitive from the toPrimitive callback (string, number, or bigint)
  2. Unwrap boxed results: return Number(x) or String(x) instead of a boxed object
  3. Fall back to valueOf/toString (delete the exotic method) if a primitive result can't be produced

Example fix

// before
const o = { [Symbol.toPrimitive]: (h) => ({ value: 1 }) };
// after
const o = { [Symbol.toPrimitive]: (h) => 1 };
Defensive patterns

Strategy: try-catch

Validate before calling

const tp = obj && obj[Symbol.toPrimitive];
if (typeof tp === 'function') { const r = tp.call(obj, 'default'); if (r !== null && typeof r === 'object') throw new Error('toPrimitive must return a primitive'); }

Try / catch

try { v = coerce(obj); } catch (e) { if (String(e).includes('convert object to primitive')) v = String(JSON.stringify(obj)); else throw e; }

Prevention

When it happens

Trigger: An object whose Symbol.toPrimitive function returns an object/array/another object, then that object is coerced (arithmetic, `+`, comparison, template string) in karate-js.

Common situations: Returning `this` or a wrapped value from a custom toPrimitive; returning a boxed Number/String object instead of a raw primitive.

Related errors


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

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/js/Terms.java:1358

        // ("string" | "number" | "default"). Result must be a primitive; an object result
        // is a TypeError per spec. Set-but-not-callable is also a TypeError (GetMethod).
        Object exotic = ol.getMember("@@toPrimitive");
        if (exotic != null && exotic != UNDEFINED) {
            if (!(exotic instanceof JsCallable jsc)) {
                throw JsErrorException.typeError("Symbol.toPrimitive method is not callable");
            }
            CoreContext callCtx = new CoreContext(context, null, null);
            callCtx.thisObject = ol;
            String hintArg = hint == null ? "default" : hint;
            Object r = jsc.call(callCtx, new Object[]{hintArg});
            if (callCtx.isError()) {
                context.updateFrom(callCtx);
                return UNDEFINED;
            }
            if (r == null || r == UNDEFINED || isPrimitive(r) || r instanceof BigInteger) {
                return r;
            }
            throw JsErrorException.typeError("Cannot convert object to primitive value");
        }
        return ordinaryToPrimitive(ol, hint, context);
    }

    /**
     * Spec §7.1.1.1 OrdinaryToPrimitive — the valueOf/toString-only dispatch
     * without the @@toPrimitive check. Used by {@link #toPrimitive} as the
     * fallback path, and by built-in @@toPrimitive methods (e.g. Date) that
     * need to invoke OrdinaryToPrimitive on themselves without re-entering
     * the @@toPrimitive lookup.
     */
    static Object ordinaryToPrimitive(ObjectLike ol, String hint, CoreContext context) {
        String[] order = "string".equals(hint)
                ? new String[]{"toString", "valueOf"}
                : new String[]{"valueOf", "toString"};
        for (String methodName : order) {
            Object fn = ol.getMember(methodName);
            if (!(fn instanceof JsCallable jsc)) {

View on GitHub (pinned to a22eb90246)