quarkusio/quarkus · error · UnsupportedOperationException

Immutable empty scope

Error message

Immutable empty scope

What it means

Scope.EMPTY is an immutable sentinel scope used at the root of template parsing; any attempt to add a binding to it throws UnsupportedOperationException("Immutable empty scope"). It exists so code can safely read from a shared, always-empty scope.

Source

Thrown at independent-projects/qute/core/src/main/java/io/quarkus/qute/Scope.java:11

package io.quarkus.qute;

import java.util.HashMap;
import java.util.Map;

public class Scope {

    public static final Scope EMPTY = new Scope(null) {
        @Override
        public void putBinding(String binding, String type) {
            throw new UnsupportedOperationException("Immutable empty scope");
        }
    };

    private final Scope parentScope;
    private Map<String, String> bindings;
    private Map<String, Object> attributes;
    // TODO: add proper API to handle this
    private String lastPartHint;

    public Scope(Scope parentScope) {
        this.parentScope = parentScope;
    }

    public void putBinding(String binding, String type) {
        if (bindings == null) {
            bindings = new HashMap<>();
        }
        bindings.put(binding, sanitizeType(type));

View on GitHub (pinned to e1c734241f)

Solutions

  1. Create a child scope (scope.child()) before calling putBinding instead of binding on the root Scope.EMPTY.
  2. Check scope == Scope.EMPTY (or a canWrite check) before putBinding and fall back to a child scope.
  3. If triggered by ordinary templates (no custom hooks), report it — the parser should have opened a mutable scope.

Example fix

// before
scope.putBinding("item", "String"); // may be Scope.EMPTY

// after
if (scope == Scope.EMPTY) {
    scope = scope.child();
}
scope.putBinding("item", "String");
Defensive patterns

Strategy: type-guard

Type guard

static boolean isMutableScope(Scope scope) {
    return scope != Scope.EMPTY;
}

Try / catch

try {
    scope.putBinding(name, type);
} catch (UnsupportedOperationException e) {
    if ("Immutable empty scope".equals(e.getMessage())) {
        scope.child().putBinding(name, type);
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling putBinding(...) on Scope.EMPTY — reached from TemplateGenerator/Parser paths such as initializeBlock, putMetadataBinding, parameterDeclaration, addParameter, or assertBinding when the current scope is the immutable empty root.

Common situations: Custom parser hooks or template extensions that add metadata/parameter declarations at the template root level where only the immutable scope exists; library bugs where a section should have created a child scope before binding.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/52e6d20515231d32. Report an issue: GitHub.