nathanmarz/storm · error · IllegalArgumentException

numBuckets must be >= 2

Error message

numBuckets must be >= 2

What it means

The TimeCacheMap constructor validates numBuckets and throws IllegalArgumentException if fewer than 2 buckets are requested. With only one bucket the expired-bucket rotation scheme cannot work (the active bucket would be cleaned immediately or never rotate), so the class forbids it at construction time.

Solutions

  1. Pass numBuckets >= 2, e.g. new TimeCacheMap(expirationSecs, 3, callback).
  2. Use the convenience constructor new TimeCacheMap(expirationSecs, callback), which defaults numBuckets to 3.
  3. Clamp computed values: int buckets = Math.max(2, computedBuckets); before constructing.
  4. Validate the config value driving numBuckets at startup and fail with a clearer message.

Example fix

// before
TimeCacheMap<String, Long> cache = new TimeCacheMap<>(expiry, 1, cb);
// after
TimeCacheMap<String, Long> cache = new TimeCacheMap<>(expiry, Math.max(2, configuredBuckets), cb);
Defensive patterns

Strategy: validation

Validate before calling

if (buckets < 2) throw new IllegalArgumentException("TimeCacheMap requires numBuckets >= 2, got " + buckets);

Try / catch

try {
    cache = new TimeCacheMap<>(expirySecs, buckets, callback);
} catch (IllegalArgumentException e) {
    LOG.warn("Invalid bucket count, using default 3");
    cache = new TimeCacheMap<>(expirySecs, callback);
}

Prevention

When it happens

Trigger: Calling new TimeCacheMap<K,V>(expirationSecs, numBuckets, callback) — or the shorter constructor defaulting numBuckets — with numBuckets < 2, e.g. new TimeCacheMap(3, 1, cb) or new TimeCacheMap(3, 0, cb).

Common situations: Passing a user-supplied or config-derived bucket count that is 0 or 1; computing numBuckets from expirationSecs/bucketInterval arithmetic that rounds down to 1; misreading defaults in code copied from an older Storm version.

Related errors


AI-assisted analysis of nathanmarz/storm@cdb116e942 (2026-09-12). Data as JSON: /api/errors/327a40cc380acfd1. Report an issue: GitHub.

Appendix: source

Thrown at storm-core/src/jvm/backtype/storm/utils/TimeCacheMap.java:54

//deprecated in favor of non-threaded RotatingMap
@Deprecated
public class TimeCacheMap<K, V> {
    //this default ensures things expire at most 50% past the expiration time
    private static final int DEFAULT_NUM_BUCKETS = 3;

    public static interface ExpiredCallback<K, V> {
        public void expire(K key, V val);
    }

    private LinkedList<HashMap<K, V>> _buckets;

    private final Object _lock = new Object();
    private Thread _cleaner;
    private ExpiredCallback _callback;
    
    public TimeCacheMap(int expirationSecs, int numBuckets, ExpiredCallback<K, V> callback) {
        if(numBuckets<2) {
            throw new IllegalArgumentException("numBuckets must be >= 2");
        }
        _buckets = new LinkedList<HashMap<K, V>>();
        for(int i=0; i<numBuckets; i++) {
            _buckets.add(new HashMap<K, V>());
        }


        _callback = callback;
        final long expirationMillis = expirationSecs * 1000L;
        final long sleepTime = expirationMillis / (numBuckets-1);
        _cleaner = new Thread(new Runnable() {
            public void run() {
                try {
                    while(true) {
                        Map<K, V> dead = null;
                        Time.sleep(sleepTime);
                        synchronized(_lock) {
                            dead = _buckets.removeLast();

View on GitHub (pinned to cdb116e942)