karatelabs/karate · error · JsErrorException (typeError)

' ' was defined without a getter

Error message

'${pn.name}' was defined without a getter

What it means

When reading a private accessor (`accessor get/set` pair) via `PrivateAccess.get`, the engine found the private name declared but only with a setter (or neither), so there is no getter to invoke. Per the ES spec, reading an accessor private field without a getter is a TypeError.

Solutions

  1. Add a getter to the accessor declaration for the private name.
  2. Remove the read expression if the member is intentionally write-only.
  3. Change the member to a plain private field (`#prop = ...`) if read-write value storage is wanted.
  4. Check that the class version being loaded actually defines the getter.

Example fix

// before
class C { accessor set #x(v) { this._x = v; } }
new C().#x; // TypeError: '#x' was defined without a getter
// after
class C { accessor #x; } // or: get #x() { return this._x; }
Defensive patterns

Strategy: type-guard

Validate before calling

// inspect the class declaration for a getter on the private accessor
const hasGetter = /get\s+#prop\b|accessor\s+#prop\b/.test(classSource);

Type guard

function privateAccessorReadable(classSrc, name) { return new RegExp('(get\\s+#' + name + '|accessor\\s+#' + name + ')').test(classSrc); }

Try / catch

try { v = obj.#prop; } catch (e) { if (e instanceof TypeError && e.message.includes('without a getter')) { /* fall back to underlying field */ } else { throw e; } }

Prevention

When it happens

Trigger: Reading `obj.#prop` (or `#prop in`-style read) where the class declared `accessor #prop` with only a `set` accessor, or declared the private name without a get accessor.

Common situations: Write-only private accessors used in expressions; a refactor that removed the getter but left read sites; partial class declarations where the accessor pair was split across class updates.

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/db17443a2d3583ed. Report an issue: GitHub.

Appendix: source

Thrown at karate-js/src/main/java/io/karatelabs/js/PrivateAccess.java:57

        PrivateName pn = context.privateEnv == null ? null : context.privateEnv.resolve(name);
        if (pn == null) {
            throw JsErrorException.syntaxError("private name " + name + " is not declared in an enclosing class");
        }
        return pn;
    }

    static boolean has(Object target, PrivateName pn) {
        return target instanceof JsObject obj && obj.hasPrivate(pn);
    }

    static Object get(Object target, PrivateName pn, CoreContext context) {
        JsObject obj = branded(target, pn, "read");
        return switch (pn.kind) {
            case FIELD -> obj.getPrivate(pn);
            case METHOD -> pn.method;
            case ACCESSOR -> {
                if (pn.getter == null) {
                    throw JsErrorException.typeError("'" + pn.name + "' was defined without a getter");
                }
                yield Interpreter.invokeGetter(pn.getter, target, context);
            }
        };
    }

    static void set(Object target, PrivateName pn, Object value, CoreContext context) {
        JsObject obj = branded(target, pn, "write");
        switch (pn.kind) {
            case FIELD -> obj.putPrivate(pn, value);
            case METHOD -> throw JsErrorException.typeError("Cannot write to private method " + pn.name);
            case ACCESSOR -> {
                if (pn.setter == null) {
                    throw JsErrorException.typeError("'" + pn.name + "' was defined without a setter");
                }
                Interpreter.invokeSetter(pn.setter, target, value, context);
            }
        }

View on GitHub (pinned to a22eb90246)