oracle/graal · error · IllegalArgumentException

Cannot modify the always-empty set

Error message

Cannot modify the always-empty set

What it means

EconomicSet.createSet() with initial capacity 0 returns the shared immutable singleton EmptySet.EMPTY_SET; add(element) on it throws IllegalArgumentException('Cannot modify the always-empty set'). checkNonNull(element) runs first, so a null element throws UnsupportedOperationException instead. The singleton is immutable by design so accidental inserts fail fast.

Source

Thrown at sdk/src/org.graalvm.collections/src/org/graalvm/collections/EmptySet.java:53

 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.graalvm.collections;

import java.util.Iterator;

/**
 * Singleton instance for empty set. Reuses empty iterator from {@link EmptyMap}.
 */
class EmptySet {
    static final EconomicSet<Object> EMPTY_SET = new EconomicSet<>() {
        @Override
        public boolean add(Object element) {
            EconomicMapImpl.checkNonNull(element);
            throw new IllegalArgumentException("Cannot modify the always-empty set");
        }

        @Override
        public void remove(Object element) {
            EconomicMapImpl.checkNonNull(element);
            throw new IllegalArgumentException("Cannot modify the always-empty set");
        }

        @Override
        public void clear() {
            throw new IllegalArgumentException("Cannot modify the always-empty set");
        }

        @Override
        public boolean contains(Object element) {
            EconomicMapImpl.checkNonNull(element);
            return false;
        }

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Allocate with positive capacity — EconomicSet.createSet(1) or the no-capacity overload — whenever the set will receive elements.
  2. Before adding, ensure you did not capture the singleton: never store the result of createSet(0) as a mutable accumulator.
  3. If emptiness is data-dependent, compute capacity as Math.max(1, expectedSize).

Example fix

// before
EconomicSet<T> s = EconomicSet.createSet(source.size()); // source empty -> singleton
s.add(item); // throws

// after
EconomicSet<T> s = EconomicSet.createSet(Math.max(1, source.size()));
s.add(item);
Defensive patterns

Strategy: validation

Validate before calling

EconomicSet<T> s = EconomicSet.createSet(Math.max(1, expectedSize)); // growable even when expectedSize == 0

Prevention

When it happens

Trigger: Calling add() on the set returned by EconomicSet.createSet(0) or EconomicSet.create(EconomicSet) from an empty source — both hand back the EMPTY_SET singleton rather than a growable set.

Common situations: Creating a set sized from another collection that happens to be empty (createSet(other.size())); dedup buffers initialized lazily with capacity from a variable that computes to 0; code ported from java.util.HashSet where add on an empty set works.

Related errors


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/881d6828982a74b2. Report an issue: GitHub.