{"record":{"id":"c0cfa0b4e38abb9e","repo":"apache/flink","slug":"cannot-copy-serializer","errorCode":null,"errorMessage":"Cannot copy serializer","messagePattern":"Cannot copy serializer","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"flink-core/src/main/java/org/apache/flink/api/java/typeutils/runtime/PojoComparator.java","lineNumber":152,"sourceCode":"        for (int i = 0; i < toClone.comparators.length; i++) {\n            this.comparators[i] = toClone.comparators[i].duplicate();\n        }\n\n        this.normalizedKeyLengths = toClone.normalizedKeyLengths;\n        this.numLeadingNormalizableKeys = toClone.numLeadingNormalizableKeys;\n        this.normalizableKeyPrefixLen = toClone.normalizableKeyPrefixLen;\n        this.invertNormKey = toClone.invertNormKey;\n\n        this.type = toClone.type;\n\n        try {\n            this.serializer =\n                    (TypeSerializer<T>)\n                            InstantiationUtil.deserializeObject(\n                                    InstantiationUtil.serializeObject(toClone.serializer),\n                                    Thread.currentThread().getContextClassLoader());\n        } catch (IOException | ClassNotFoundException e) {\n            throw new RuntimeException(\"Cannot copy serializer\", e);\n        }\n    }\n\n    private void writeObject(ObjectOutputStream out) throws IOException, ClassNotFoundException {\n        out.defaultWriteObject();\n        out.writeInt(keyFields.length);\n        for (Field field : keyFields) {\n            FieldSerializer.serializeField(field, out);\n        }\n    }\n\n    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {\n        in.defaultReadObject();\n        int numKeyFields = in.readInt();\n        keyFields = new Field[numKeyFields];\n        for (int i = 0; i < numKeyFields; i++) {\n            keyFields[i] = FieldSerializer.deserializeField(in);\n        }","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/apache/flink/blob/2f3c205e9266cb30240eb7f4fdab15cad629a70f/flink-core/src/main/java/org/apache/flink/api/java/typeutils/runtime/PojoComparator.java#L134-L170","documentation":"PojoComparator's copy constructor clones its PojoSerializer by Java-serializing and deserializing it (InstantiationUtil round-trip with the context classloader). If the serializer cannot be Java-serialized or its class is not loadable by the thread-context classloader, this RuntimeException is thrown.","triggerScenarios":"comparator.duplicate() on a PojoComparator where the wrapped PojoSerializer contains state (e.g. cached Kryo instance or custom serializer classes) that fails Java serialization, or when Thread.currentThread().getContextClassLoader() cannot see the serializer's class - typical in classloader-isolated deployments (sessions, libraries directory, SQL planner loader).","commonSituations":"Running jobs on mini-cluster/session with user-code classloader separation; Kryo serializer cached inside PojoSerializer is not serializable; ClassNotFoundException after moving POJO/serializer classes between job submissions; custom subclasses of PojoSerializer not on the context classloader.","solutions":["Check the cause: NotSerializableException points at the non-serializable field to make transient/serializable; ClassNotFoundException points at a classloader issue","Ensure POJO and any custom serializer classes are inside the job jar (not only on the server classpath) so the user-code classloader can load them","Mark non-serializable cached resources (like Kryo instances) transient and lazily reinitialize them in readObject/open"],"exampleFix":"// before\nclass MyPojoSerializer extends TypeSerializer<MyPojo> {\n    private final Kryo kryo = new Kryo(); // not serializable\n}\n\n// after\nclass MyPojoSerializer extends TypeSerializer<MyPojo> implements Serializable {\n    private transient Kryo kryo;\n    private Kryo kryo() {\n        if (kryo == null) kryo = new Kryo();\n        return kryo;\n    }\n}","handlingStrategy":"try-catch","validationCode":"// Verify the comparator's serializer can be Java-serialized before duplicate()\ntry {\n    InstantiationUtil.serializeObject(comparator.getSerializer());\n} catch (IOException e) {\n    throw new IllegalStateException(\"Serializer not Java-serializable: \" + e, e);\n}","typeGuard":null,"tryCatchPattern":"try {\n    TypeComparator<T> dup = comparator.duplicate();\n} catch (RuntimeException e) {\n    Throwable cause = e.getCause();\n    if (cause instanceof ClassNotFoundException) {\n        throw new IllegalStateException(\"Serializer class not on context classloader\", cause);\n    }\n    throw e;\n}","preventionTips":["Ship POJO and custom serializer classes inside the job jar","Make custom serializers Java-Serializable with transient Kryo/resources","Avoid capturing non-serializable runtime objects in comparator serializers"],"tags":["pojo","comparator","java-serialization","classloader"],"backgroundTag":null,"analyzedSha":"2f3c205e9266cb30240eb7f4fdab15cad629a70f","analyzedAt":"2026-08-14T08:48:24.518Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}