nathanmarz/storm · error · IllegalArgumentException

Combiner state updater should receive a single tuple…

Error message

Combiner state updater should receive a single tuple. Received: ${tuples}

What it means

CombinerAggStateUpdater performs state.update(new CombinerValueUpdater(agg, value)) which needs exactly one new value per call, so updateState asserts tuples.size()==1 and throws IllegalArgumentException otherwise. Combiner aggregators collapse a value, unlike reducers that accept a batch of tuples.

Solutions

  1. Use ReducerAggStateUpdater / ReducerAggregator semantics if you must process multiple tuples per update, or pre-combine upstream
  2. Ensure the stream feeding the combiner updater is grouped/partitioned so exactly one tuple reaches updateState
  3. Switch the operation to a CombinerAggregator-based persistentAggregate which guarantees single-value updates

Example fix

// before
stream.groupBy(g).persistentAggregate(state, new Fields("v"), new ReducerAggregator(), new MapCombinerAggStateUpdater(...));
// after
stream.groupBy(g).persistentAggregate(state, new Fields("v"), new Count(), new MapCombinerAggStateUpdater<>(new Count()));
Defensive patterns

Strategy: validation

Validate before calling

if (tuples == null || tuples.size() != 1) {
    throw new IllegalArgumentException("CombinerAggStateUpdater requires exactly one tuple, got " + (tuples == null ? 0 : tuples.size()));
}

Type guard

boolean isSingleTuple(List<TridentTuple> tuples) {
    return tuples != null && tuples.size() == 1;
}

Try / catch

try {
    updater.updateState(state, tuples, collector);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("should receive a single tuple")) {
        throw new IllegalStateException("Use a Reducer-style updater for multi-tuple batches", e);
    } throw e;
}

Prevention

When it happens

Trigger: A persistentAggregate(newMapState, ..., new CombinerAggregator..., new MapCombinerAggStateUpdater...) whose upstream grouping/projection delivers 0 or >1 tuples to a single updateState invocation.

Common situations: Mis-wiring a combiner updater into a reducer-style aggregate chain; upstream partitioning collapsing/expanding tuples so multiple values arrive at once; hand-rolled state update calls passing a batch list.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at storm-core/src/jvm/storm/trident/operation/impl/CombinerAggStateUpdater.java:42

import storm.trident.operation.TridentCollector;
import storm.trident.operation.TridentOperationContext;
import storm.trident.state.CombinerValueUpdater;
import storm.trident.state.StateUpdater;
import storm.trident.state.snapshot.Snapshottable;
import storm.trident.tuple.TridentTuple;

public class CombinerAggStateUpdater implements StateUpdater<Snapshottable> {
    CombinerAggregator _agg;
    
    public CombinerAggStateUpdater(CombinerAggregator agg) {
        _agg = agg;
    }
    

    @Override
    public void updateState(Snapshottable state, List<TridentTuple> tuples, TridentCollector collector) {
        if(tuples.size()!=1) {
            throw new IllegalArgumentException("Combiner state updater should receive a single tuple. Received: " + tuples.toString());
        }
        Object newVal = state.update(new CombinerValueUpdater(_agg, tuples.get(0).getValue(0)));
        collector.emit(new Values(newVal));
    }

    @Override
    public void prepare(Map conf, TridentOperationContext context) {        
    }

    @Override
    public void cleanup() {
    }
    
}

View on GitHub (pinned to cdb116e942)