alibaba/spring-ai-alibaba · error · IllegalArgumentException

threadId is not allowed to be null

Error message

threadId is not allowed to be null

What it means

MongoSaver.list() requires a checkpoint thread to operate on; the threadId inside the supplied RunnableConfig must be present. If config.threadId() returns an empty Optional, the saver cannot scope the Mongo query to a thread and throws this IllegalArgumentException immediately, before any database session is opened.

Source

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

		Document metaDoc = threadMetaCollection.find(clientSession, new BasicDBObject("_id", metaId)).first();

		if (metaDoc != null) {
			String threadId = metaDoc.getString(FIELD_THREAD_ID);
			Boolean isReleased = metaDoc.getBoolean(FIELD_IS_RELEASED, false);

			if (threadId != null && !Boolean.TRUE.equals(isReleased)) {
				return threadId;
			}
		}

		return null; // No active thread exists
	}

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

		String threadName = threadNameOpt.get();
		ClientSession clientSession = this.client
				.startSession(ClientSessionOptions.builder().defaultTransactionOptions(txnOptions).build());
		clientSession.startTransaction();
		List<Checkpoint> checkpoints = null;
		try {
			// Get active thread_id for the thread_name
			String threadId = getActiveThreadId(threadName, clientSession);
			if (threadId == null) {
				clientSession.commitTransaction();
				return Collections.emptyList();
			}

			// Use thread_id to query checkpoints
			MongoCollection<Document> collection = database.getCollection(CHECKPOINT_COLLECTION);
			String checkpointId = CHECKPOINT_PREFIX + threadId;

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Build the RunnableConfig with a non-null threadId before calling list(), e.g. RunnableConfig.builder().threadId("my-thread").build()
  2. Call config.threadId() (or Optional.isPresent) yourself before invoking list() and fail fast with a clear application-level message
  3. If the thread is genuinely unknown, do not call list(); list all threads via the release/tag APIs or initialize a new thread by putting a checkpoint first

Example fix

// before
saver.list(RunnableConfig.builder().build());
// after
saver.list(RunnableConfig.builder().threadId("session-123").build());
Defensive patterns

Strategy: validation

Validate before calling

RunnableConfig cfg = /* ... */;
if (cfg == null || cfg.threadId() == null || !cfg.threadId().isPresent()) {
    throw new IllegalArgumentException("list() requires a non-null threadId");
}
saver.list(cfg);

Type guard

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

Try / catch

try {
    saver.list(cfg);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("threadId")) {
        throw new IllegalStateException("RunnableConfig must include threadId", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling MongoSaver.list(config) with a RunnableConfig that was built without a threadId, or one whose threadId was explicitly set to null.

Common situations: Building a RunnableConfig programmatically and forgetting threadId; reusing a default/empty config object; a null threadId propagating from an upstream API that treated 'no thread' as a valid state.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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