ruby-concurrency/concurrent-ruby · error · IllegalArgumentException

IllegalArgumentException

Error message

IllegalArgumentException

What it means

The single-argument constructor treats initialCapacity as a sizing hint and converts it to a power-of-two table size via tableSizeFor. A negative capacity cannot be a table size, so the constructor throws IllegalArgumentException before any table is allocated. The exception carries no message; the stack frame at this constructor line is the only diagnostic.

Source

Thrown at ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java:2536

     * Creates a new, empty map with the default initial table size (16).
     */
    public ConcurrentHashMapV8() {
        this.counter = new LongAdder();
    }

    /**
     * Creates a new, empty map with an initial table size
     * accommodating the specified number of elements without the need
     * to dynamically resize.
     *
     * @param initialCapacity The implementation performs internal
     * sizing to accommodate this many elements.
     * @throws IllegalArgumentException if the initial capacity of
     * elements is negative
     */
    public ConcurrentHashMapV8(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                MAXIMUM_CAPACITY :
                tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        this.counter = new LongAdder();
        this.sizeCtl = cap;
    }

    /**
     * Creates a new map with the same mappings as the given map.
     *
     * @param m the map
     */
    public ConcurrentHashMapV8(Map<? extends K, ? extends V> m) {
        this.counter = new LongAdder();
        this.sizeCtl = DEFAULT_CAPACITY;
        internalPutAll(m);
    }

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Clamp before constructing: use Math.max(0, requestedCapacity) or fall back to a default like 16 when negative
  2. Validate numeric configuration once at load time and fail fast with a clear message instead of at first construction
  3. Treat the -1 sentinel as an instruction to use the no-arg constructor

Example fix

// before
int cap = configuredCapacity; // -1 when unset
new ConcurrentHashMapV8<String,String>(cap);

// after
int cap = configuredCapacity > 0 ? configuredCapacity : 16;
new ConcurrentHashMapV8<String,String>(cap);
Defensive patterns

Strategy: validation

Validate before calling

int cap = (configuredCapacity != null && configuredCapacity > 0) ? configuredCapacity : DEFAULT_CAPACITY;
// then construct with cap

Try / catch

try { new ConcurrentHashMapV8<K,V>(cap); } catch (IllegalArgumentException e) { throw new ConfigException(cap); }

Prevention

When it happens

Trigger: new ConcurrentHashMapV8<K,V>(-1); passing a computed size such as expected - slack that underflows below zero; forwarding a configuration value that uses -1 as its unset sentinel straight into the constructor.

Common situations: Plumbing capacity from YAML/env/config where the default is -1 meaning not-set; boundary-value unit tests; refactoring HashMap initialCapacity code paths into the concurrent map during a caching migration.

Related errors


AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21). Data as JSON: /api/errors/7a2ee85c832b7dd0. Report an issue: GitHub.