alibaba/spring-ai-alibaba · error · IllegalArgumentException

threadId is not allow null

Error message

threadId is not allow null

What it means

MongoSaver.get(config) looks up the latest checkpoint for a given thread; it needs config.threadId() to identify that thread. When the Optional is empty it throws IllegalArgumentException ('threadId is not allow null') before touching MongoDB. Note the message differs slightly from list()'s ('not allow null' vs 'not allowed to be null') but the cause is identical.

Source

Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/mongo/MongoSaver.java:334

			String checkpointsStr = document.getString(DOCUMENT_CONTENT_KEY);
			checkpoints = deserializeCheckpoints(checkpointsStr);
			clientSession.commitTransaction();
		}
		catch (Exception e) {
			clientSession.abortTransaction();
			throw new RuntimeException(e);
		}
		finally {
			clientSession.close();
		}
		return checkpoints;
	}

	@Override
	public Optional<Checkpoint> get(RunnableConfig config) {
		Optional<String> threadNameOpt = config.threadId();
		if (!threadNameOpt.isPresent()) {
			throw new IllegalArgumentException("threadId is not allow null");
		}

		String threadName = threadNameOpt.get();
		ClientSession clientSession = this.client
				.startSession(ClientSessionOptions.builder().defaultTransactionOptions(txnOptions).build());
		LinkedList<Checkpoint> checkpoints = null;
		try {
			clientSession.startTransaction();

			// Get active thread_id for the thread_name
			String threadId = getActiveThreadId(threadName, clientSession);
			if (threadId == null) {
				clientSession.commitTransaction();
				return Optional.empty();
			}

			// Use thread_id to query checkpoints
			MongoCollection<Document> collection = database.getCollection(CHECKPOINT_COLLECTION);

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Pass a config with a valid threadId: RunnableConfig.builder().threadId("thread-name").build()
  2. Guard the call site with config.threadId().isPresent() and return Optional.empty() or throw a domain-specific error
  3. If you need 'latest checkpoint of any thread', query the checkpoint collection separately instead of calling get() with a null thread

Example fix

// before
Optional<Checkpoint> cp = saver.get(new RunnableConfig());
// after
Optional<Checkpoint> cp = saver.get(RunnableConfig.builder().threadId("thread-1").build());
Defensive patterns

Strategy: validation

Validate before calling

if (config == null || !config.threadId().isPresent()) {
    return Optional.empty(); // or fail fast
}
Optional<Checkpoint> cp = saver.get(config);

Type guard

boolean hasThreadId(RunnableConfig cfg) {
    return cfg != null && cfg.threadId() != null && cfg.threadId().isPresent();
}

Try / catch

try {
    return saver.get(cfg);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("threadId")) return Optional.empty();
    throw e;
}

Prevention

When it happens

Trigger: Calling MongoSaver.get(config) where the RunnableConfig was constructed without a threadId or with an explicit null threadId.

Common situations: Restoring graph state with an empty default config; a code path that builds config dynamically and drops the threadId; copy-pasted config builders where threadId was never set.

Related errors


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