alibaba/spring-ai-alibaba · error · RuntimeException
Failed to deserialize checkpoints
Error message
Failed to deserialize checkpoints
What it means
RedisSaver.list catches IOException | ClassNotFoundException thrown by deserializeCheckpoints and rethrows them as RuntimeException('Failed to deserialize checkpoints'). It means raw checkpoint bytes read from Redis could not be deserialized back into Checkpoint objects — typically a serialization format or classpath mismatch.
Source
Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/redis/RedisSaver.java:254
if (!tryLock) {
return List.of();
}
// Get active thread_id for the thread_name
String threadId = getActiveThreadId(threadName);
if (threadId == null) {
return List.of();
}
// Use thread_id to query checkpoints
return deserializeCheckpoints(CHECKPOINT_PREFIX + threadId);
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("Failed to deserialize checkpoints", e);
}
finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
@Override
public Optional<Checkpoint> get(RunnableConfig config) {
Optional<String> threadNameOpt = config.threadId();
if (!threadNameOpt.isPresent()) {
throw new IllegalArgumentException("threadId isn't allow null");
}
String threadName = threadNameOpt.get();
RLock lock = redisson.getLock(LOCK_PREFIX + threadName);
boolean tryLock = false;View on GitHub (pinned to f82da0b50f)
Solutions
- Check the chained cause for ClassNotFoundException — restore or add the missing class/version to the classpath.
- Flush stale checkpoints for affected threads (delete CHECKPOINT_PREFIX + threadId keys) and let the graph recreate them.
- Use the same StateSerializer consistently across all writers and readers of the same Redis database.
- Pin a stable serialVersionUID (or switch to JSON serialization) for custom state classes to survive refactors.
Example fix
// before
StateSerializer serializer = new JDKStateSerializer<>(stateFactory); // was Jackson before
// after
StateSerializer serializer = StateGraph.DEFAULT_JACKSON_SERIALIZER; // match writer's serializer
// and/or clear stale keys: redisTemplate.delete("checkpoints:" + threadId); Defensive patterns
Strategy: fallback
Try / catch
try { return saver.list(config); } catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().contains("Failed to deserialize")) {
log.warn("Incompatible stale checkpoints; starting fresh", e);
return List.of(); // or purge keys and continue
}
throw e;
} Prevention
- Keep one StateSerializer across versions
- Fix serialVersionUID on custom state classes
- Purge Redis checkpoint keys on incompatible upgrades
- Test deserialization of persisted checkpoints in CI
When it happens
Trigger: Checkpoints written by a different serializer version or different StateSerializer (e.g. JDK vs Jackson), state classes that changed package/name after a refactor, or corrupted/stale Redis keys from an older app version.
Common situations: Upgrading the app or spring-ai-alibaba version while old checkpoints persist in Redis; serialVersionUID changes on custom state objects; switching StateGraph serializer between runs; manual Redis data migrations.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- Failed to serialize/deserialize checkpoints
- bytes cannot be empty
- UpdatePluginError
- Content Type used for store state '%s' is different from one
- Unable to load checkpoint
AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09).
Data as JSON: /api/errors/98351955e6e545c0.
Report an issue: GitHub.