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
- Create a child scope (scope.child()) before calling putBinding instead of binding on the root Scope.EMPTY.
- Check scope == Scope.EMPTY (or a canWrite check) before putBinding and fall back to a child scope.
- 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
- Always create a child scope before binding in custom parser hooks
- Check scope != Scope.EMPTY before mutating
- Root-level metadata should go through supported APIs, not direct binding writes
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
- UnsupportedOperationException
- Unable to render config roots for top level prefix: ${topLev
- Unable to render config roots for specific file: ${fileName}
- Unable to render config section for section: ${section} in e
- Unable to render all config
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/52e6d20515231d32.
Report an issue: GitHub.