apache/flink · error · IllegalStateException

Concurrent access to KryoSerializer. Thread 1: {threadName1}

Error message

Concurrent access to KryoSerializer. Thread 1: {threadName1} , Thread 2: {threadName2}

What it means

KryoSerializer is not thread-safe; it tracks the current accessing thread with a best-effort check (enterExclusiveThread/exitExclusiveThread) around serialize/deserialize. If serialize() or deserialize() is entered while a different thread is already recorded (previous != null && previous != thisThread), it throws IllegalStateException naming both threads. Note the check is deliberately non-atomic, so genuine races may instead produce corrupted Kryo state rather than this exception.

Source

Thrown at flink-core/src/main/java/org/apache/flink/api/java/typeutils/runtime/kryo/KryoSerializer.java:706

                    "Could not clone serializer instance of class " + original.getClass(), ex);
        }
    }

    // --------------------------------------------------------------------------------------------
    // For testing
    // --------------------------------------------------------------------------------------------

    private void enterExclusiveThread() {
        // we use simple get, check, set here, rather than CAS
        // we don't need lock-style correctness, this is only a sanity-check and we thus
        // favor speed at the cost of some false negatives in this check
        Thread previous = currentThread;
        Thread thisThread = Thread.currentThread();

        if (previous == null) {
            currentThread = thisThread;
        } else if (previous != thisThread) {
            throw new IllegalStateException(
                    "Concurrent access to KryoSerializer. Thread 1: "
                            + thisThread.getName()
                            + " , Thread 2: "
                            + previous.getName());
        }
    }

    private void exitExclusiveThread() {
        currentThread = null;
    }

    @VisibleForTesting
    public Kryo getKryo() {
        checkKryoInitialized();
        return this.kryo;
    }
}

View on GitHub (pinned to 2f3c205e92)

Solutions

  1. Give each thread its own serializer: call serializer.duplicate() (TypeSerializer API) per thread instead of sharing one instance.
  2. Remove static/shared caching of the TypeSerializer — obtain it per subtask/per thread from the TypeInformation.
  3. Synchronize external access if duplication is impossible: wrap serialize/deserialize calls in a lock so only one thread enters at a time.
  4. In custom Kryo serializers, never re-enter the outer serializer; use the Kryo instance passed to the callback.

Example fix

// before
private static final TypeSerializer<MyEvent> SER = TypeInfoFactory...createSerializer();
// shared across pool threads -> IllegalStateException

// after
private final TypeSerializer<MyEvent> ser = baseSerializer.duplicate(); // one per thread/subtask
Defensive patterns

Strategy: validation

Validate before calling

// Before sharing, give each thread its own copy — TypeSerializer.duplicate() exists for this.
TypeSerializer<T> threadLocalSer = baseSer.duplicate();

Prevention

When it happens

Trigger: Two threads calling serialize()/deserialize() on the SAME KryoSerializer instance concurrently; sharing one serializer instance across parallel subtasks, a thread pool, or an async snapshotting thread plus the mailbox thread; re-entrant serialization from a custom Kryo serializer that itself uses the same KryoSerializer.

Common situations: Caching a TypeSerializer in a static/instance field used by multiple worker threads in an application or in a source/sink that serializes from an I/O thread; reusing serializers across operator subtasks instead of duplicating them; using the same KryoSerializer from a Kafka callback thread and the main pipeline thread.

Related errors


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