apache/flink · critical · IllegalStateException

The window uses a merging assigner, but the window state is

Error message

The window uses a merging assigner, but the window state is not mergeable.

What it means

Sanity check during TwoOutputWindowProcessOperator initialization (windows with a main and side output). When the assigner is a MergingWindowAssigner (session windows), the per-window state must implement InternalMergingState to merge contents of merged-away windows; a non-null windowState that fails the instanceof check makes open() throw IllegalStateException, so the job fails to start. Standard backends (hashmap, RocksDB/ForSt) satisfy this, pointing to a custom backend or state registration as the cause.

Source

Thrown at flink-datastream/src/main/java/org/apache/flink/datastream/impl/extension/window/operators/TwoOutputWindowProcessOperator.java:175

        // NOTE - the state may be null in the case of the overriding evicting window operator
        if (windowStateDescriptor != null) {
            windowState =
                    getOrCreateKeyedState(
                            windowSerializer.createInstance(),
                            windowSerializer,
                            windowStateDescriptor);
        }

        // create the typed and helper states for merging windows
        if (windowAssigner instanceof MergingWindowAssigner) {

            // store a typed reference for the state of merging windows - sanity check
            if (windowState instanceof InternalMergingState) {
                windowMergingState =
                        (InternalMergingState<K, W, IN, IN, StateIterator<IN>, Iterable<IN>>)
                                windowState;
            } else if (windowState != null) {
                throw new IllegalStateException(
                        "The window uses a merging assigner, but the window state is not mergeable.");
            }

            @SuppressWarnings("unchecked")
            final Class<Tuple2<W, W>> typedTuple = (Class<Tuple2<W, W>>) (Class<?>) Tuple2.class;

            final TupleSerializer<Tuple2<W, W>> tupleSerializer =
                    new TupleSerializer<>(
                            typedTuple, new TypeSerializer[] {windowSerializer, windowSerializer});

            final ListStateDescriptor<Tuple2<W, W>> mergingSetsStateDescriptor =
                    new ListStateDescriptor<>("merging-window-set", tupleSerializer);

            // get the state that stores the merging sets
            mergingSetsState =
                    getOrCreateKeyedState(
                            VoidNamespaceSerializer.INSTANCE.createInstance(),
                            VoidNamespaceSerializer.INSTANCE,

View on GitHub (pinned to 2f3c205e92)

Solutions

  1. Run the job on 'hashmap' or 'rocksdb'/'forst'.
  2. Make the custom backend's list states implement InternalMergingState, or drop session windows for that backend.
  3. Keep the window state descriptor a plain ListStateDescriptor end-to-end.
  4. Align flink-datastream-api and flink-dist versions.

Example fix

// before: session window, two-output function, non-merging backend
stream.process(twoOutputFn, WindowStrategy.session(Duration.ofMinutes(5)));
// after: non-merging strategy or merging-capable backend
stream.process(twoOutputFn, WindowStrategy.tumbling(Duration.ofMinutes(5), WindowStrategy.EVENT_TIME));
Defensive patterns

Strategy: validation

Validate before calling

// setup guard for two-output session jobs
if (strategy instanceof SessionWindowStrategy
        && !Arrays.asList("hashmap", "rocksdb", "forst").contains(backendName)) {
    throw new IllegalArgumentException("two-output session windows need a mergeable backend");
}

Type guard

static boolean requiresMergingState(WindowStrategy s) {
    return s instanceof SessionWindowStrategy;
}

Prevention

When it happens

Trigger: A two-output windowed stream (e.g. WindowProcessFunction emitting to main + side collectors) built with WindowStrategy.session(...) while the keyed state for window contents is not an InternalMergingState - custom KeyedStateBackend, decorated state, or unsupported backend flavor.

Common situations: Pluggable state backends; jobs migrated from heap to a custom backend without checking merging support; mixed Flink versions; state factories wrapping returned states.

Related errors


AI-assisted analysis of apache/flink@2f3c205e92 (2026-08-14). Data as JSON: /api/errors/7c2a88897e707295. Report an issue: GitHub.