apache/iceberg · critical · IOException

Could not deserialize the WriteResult object

Error message

Could not deserialize the WriteResult object

What it means

WriteResultSerializer.deserialize wraps ClassNotFoundException from InstantiationUtil.deserializeObject into this IOException. The WriteResult bytes are valid enough to read, but the WriteResult class (or a nested class) cannot be loaded in the current JVM — a classpath/version mismatch when restoring state.

Source

Thrown at flink/v2.2/flink/src/main/java/org/apache/iceberg/flink/sink/WriteResultSerializer.java:58

  public byte[] serialize(WriteResult writeResult) throws IOException {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    DataOutputViewStreamWrapper view = new DataOutputViewStreamWrapper(out);
    byte[] result = InstantiationUtil.serializeObject(writeResult);
    view.write(result);
    return out.toByteArray();
  }

  @Override
  public WriteResult deserialize(int version, byte[] serialized) throws IOException {
    if (version == 1) {
      DataInputDeserializer view = new DataInputDeserializer(serialized);
      byte[] resultBuf = new byte[serialized.length];
      view.read(resultBuf);
      try {
        return InstantiationUtil.deserializeObject(
            resultBuf, IcebergCommittableSerializer.class.getClassLoader());
      } catch (ClassNotFoundException cnc) {
        throw new IOException("Could not deserialize the WriteResult object", cnc);
      }
    }
    throw new IOException("Unrecognized version or corrupt state: " + version);
  }
}

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Ensure the exact iceberg-flink-runtime version that wrote the state is on the classpath of JobManager and TaskManagers.
  2. Fix shading conflicts: mark Iceberg dependencies provided in the job jar so the cluster's version is used consistently.
  3. Resume from a savepoint produced by the current version (stop-with-savepoint --drain) instead of old state.
  4. If using Flink's classloader options, set classloader.check-leaked-classloader / child-first consistently across versions.

Example fix

// before (pom.xml)
<dependency>org.apache.iceberg:iceberg-flink-runtime-1.19:compile</dependency>
// after
<dependency>
  <groupId>org.apache.iceberg</groupId>
  <artifactId>iceberg-flink-runtime-1.19</artifactId>
  <scope>provided</scope>
</dependency>
Defensive patterns

Strategy: type-guard

Validate before calling

// verify WriteResult is loadable on the cluster classpath before resuming
try {
  Class.forName("org.apache.iceberg.flink.sink.WriteResult");
} catch (ClassNotFoundException e) {
  throw new IllegalStateException("iceberg-flink runtime jar missing from classpath", e);
}

Type guard

static boolean writeResultLoadable() {
  try {
    WriteResultSerializer.class.getClassLoader().loadClass("org.apache.iceberg.flink.sink.WriteResult");
    return true;
  } catch (ClassNotFoundException e) {
    return false;
  }
}

Try / catch

try {
  env.execute();
} catch (Exception e) {
  Throwable root = ExceptionUtils.findThrowable(e, ClassNotFoundException.class).orElse(null);
  if (root != null && root.getName().contains("WriteResult")) {
    // fix runtime jar/shading, then resubmit
  }
  throw e;
}

Prevention

When it happens

Trigger: Restoring a Flink checkpoint/savepoint whose WriteResult state was written with a different Iceberg version whose WriteResult class isn't loadable (missing iceberg-flink-runtime jar on TaskManagers, shaded duplicate classes, or version downgrade).

Common situations: Job jar shading Iceberg differently than the runtime; TaskManager classpath missing the iceberg jars after a cluster upgrade; resuming state across incompatible Iceberg releases.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/1c2b5449667a001f. Report an issue: GitHub.