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

  1. Check the chained cause for ClassNotFoundException — restore or add the missing class/version to the classpath.
  2. Flush stale checkpoints for affected threads (delete CHECKPOINT_PREFIX + threadId keys) and let the graph recreate them.
  3. Use the same StateSerializer consistently across all writers and readers of the same Redis database.
  4. 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

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


AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09). Data as JSON: /api/errors/98351955e6e545c0. Report an issue: GitHub.