{"record":{"id":"bdafc1e1b1553f52","repo":"apache/flink","slug":"unable-to-deserialize-default-value","errorCode":null,"errorMessage":"Unable to deserialize default value.","messagePattern":"Unable to deserialize default value\\.","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"flink-core/src/main/java/org/apache/flink/api/common/state/StateDescriptor.java","lineNumber":444,"sourceCode":"\n        // read the default value field\n        boolean hasDefaultValue = in.readBoolean();\n        if (hasDefaultValue) {\n            TypeSerializer<T> serializer = serializerAtomicReference.get();\n            checkNotNull(serializer, \"Serializer not initialized.\");\n\n            int size = in.readInt();\n\n            byte[] buffer = new byte[size];\n\n            in.readFully(buffer);\n\n            try (ByteArrayInputStream bais = new ByteArrayInputStream(buffer);\n                    DataInputViewStreamWrapper inView = new DataInputViewStreamWrapper(bais)) {\n\n                defaultValue = serializer.deserialize(inView);\n            } catch (Exception e) {\n                throw new IOException(\"Unable to deserialize default value.\", e);\n            }\n        } else {\n            defaultValue = null;\n        }\n    }\n}\n","sourceCodeStart":426,"sourceCodeEnd":451,"githubUrl":"https://github.com/apache/flink/blob/2f3c205e9266cb30240eb7f4fdab15cad629a70f/flink-core/src/main/java/org/apache/flink/api/common/state/StateDescriptor.java#L426-L451","documentation":"During Java deserialization of the StateDescriptor (readObject), Flink rebuilds defaultValue by deserializing the stored bytes via serializer.deserialize(inView). If deserialization throws (serializer mismatch, corrupted bytes, version skew between the serializer that wrote the bytes and the one reading them), the failure is wrapped in an IOException.","triggerScenarios":"Restoring a StateDescriptor from a checkpoint/savepoint where the default-value bytes were written by a different or older serializer; corrupted serialized descriptor bytes; a custom serializer whose read path does not match its write path.","commonSituations":"Upgrading the state value class or serializer after a savepoint was taken; Kryo/POJO serializer config drift between the job that wrote the descriptor and the one restoring it; transferring descriptors across Flink versions; classpath differences that load a different serializer implementation.","solutions":["Keep the TypeSerializer and the state value class stable across the checkpoint lifecycle; implement a proper TypeSerializerSnapshot with resolveSchemaCompatibility for upgrade paths.","When changing the value class, provide a migration path (registered serializers / state processor API) before restoring from the old savepoint.","Verify the serializer snapshot version matches (getCurrentVersion / readSnapshot) and that serialize/deserialize are symmetric in your custom serializer.","If the default value is optional, consider removing it from the descriptor so no cross-version bytes need to survive."],"exampleFix":"// before: restored savepoint has old MyPojoV1 default bytes, current class is MyPojoV2\n\n// after: register a serializer snapshot upgrade path\nenv.getConfig().registerTypeWithKryoSerializer(MyPojoV2.class, new MyPojoV2Serializer());\n// and implement TypeSerializerSnapshot<MyPojoV2> with resolveSchemaCompatibility","handlingStrategy":"try-catch","validationCode":"// Before restore, verify the serializer snapshot version is compatible\nTypeSerializerSnapshot<T> snap = currentSerializer.snapshotConfiguration();\nif (snap.getCurrentVersion() != expectedVersion) {\n    // implement a migration path before restoring\n}\n// Validate round-trip symmetry in tests\nByteArrayOutputStream out = new ByteArrayOutputStream();\nser.serialize(defaultValue, new DataOutputViewStreamWrapper(out));\nT back = ser.deserialize(new DataInputViewStreamWrapper(\n    new ByteArrayInputStream(out.toByteArray())));\nassert Objects.equals(defaultValue, back);","typeGuard":"// Ensure custom serializer implements a proper snapshot with resolveSchemaCompatibility\nTypeSerializerSnapshot<T> snap = serializer.snapshotConfiguration();\nCompatibilityResult compat = snap.resolveSchemaCompatibility(oldSnap);\nif (!compat.isCompatible()) {\n    // provide migration before restore\n}","tryCatchPattern":"// Restore failures typically surface at job startup; catch at the deploy boundary\ntry {\n    env.execute();\n} catch (Exception e) {\n    if (e.getMessage().contains(\"Unable to deserialize default value\")) {\n        // run the State Processor API to migrate the savepoint,\n        // or align the serializer version before retrying\n    }\n}","preventionTips":["Implement TypeSerializerSnapshot for custom serializers with a correct getCurrentVersion and resolveSchemaCompatibility.","Test savepoint restore across serializer versions in CI before upgrading.","Keep the state value class and serializer stable; plan migrations explicitly."],"tags":["state-descriptor","deserialization","checkpoint-restore","type-serializer","version-skew"],"backgroundTag":null,"analyzedSha":"2f3c205e9266cb30240eb7f4fdab15cad629a70f","analyzedAt":"2026-08-14T08:48:24.518Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}