alibaba/spring-ai-alibaba · error · IllegalStateException

Thread already exists:

Error message

Thread already exists: 

What it means

ThreadServiceImpl.createThread builds a map key from appName/userId/threadId and throws IllegalStateException when the in-memory threads map already contains that key. The library enforces thread-ID uniqueness per app/user, refusing to silently overwrite an existing conversation thread. Callers must generate a fresh threadId or reuse the existing thread instead of calling createThread again.

Source

Thrown at spring-ai-alibaba-studio/src/main/java/com/alibaba/cloud/ai/agent/studio/service/ThreadServiceImpl.java:90

			return ListThreadsResponse.of(userThreads);
		});
	}

	@Override
	public Mono<Thread> createThread(
			String appName, String userId, Map<String, Object> initialState, String threadId) {
		return Mono.fromCallable(() -> {
			// Generate thread ID if not provided
			String finalThreadId = (threadId == null || threadId.trim().isEmpty())
					? generateThreadId()
					: threadId;

			String key = buildKey(appName, userId, finalThreadId);

			// Check if thread already exists
			if (threads.containsKey(key)) {
				log.warn("Attempted to create duplicate thread: {}", finalThreadId);
				throw new IllegalStateException("Thread already exists: " + finalThreadId);
			}

			// Create new thread
			Thread newThread = Thread.builder(finalThreadId)
					.appName(appName)
					.userId(userId)
					.build();

			threads.put(key, newThread);

			// Store initial state if provided
			if (initialState != null && !initialState.isEmpty()) {
				thradStates.put(key, new ConcurrentHashMap<>(initialState));
			}

			log.info("Created thread: {} for app={}, user={}", finalThreadId, appName, userId);
			return newThread;
		});

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Generate a unique threadId (e.g., UUID.randomUUID()) per conversation before calling createThread.
  2. Check thread existence first via a get/threads lookup (or the same buildKey containsKey check) and reuse the existing thread instead of creating.
  3. If retrying after failure, use the same threadId only when the previous create truly succeeded, otherwise issue a new threadId.
  4. Wrap the call in try-catch for IllegalStateException and treat it as 'thread already initialized' by fetching the existing thread.

Example fix

// before
threadService.createThread(appName, userId, "main-thread"); // throws on restart/retry
// after
String threadId = UUID.randomUUID().toString();
threadService.createThread(appName, userId, threadId);
Defensive patterns

Strategy: try-catch

Validate before calling

if (threadService.getThread(appName, userId, threadId) != null) { /* reuse existing */ }

Try / catch

try {
    threadService.createThread(appName, userId, threadId);
} catch (IllegalStateException e) {
    if (e.getMessage().startsWith("Thread already exists")) {
        // reuse existing thread
    } else { throw e; }
}

Prevention

When it happens

Trigger: Calling ThreadService.createThread with (appName, userId, threadId) triple whose key already exists in the threads map — i.e., a second createThread call with the same threadId for the same app and user.

Common situations: Client retry after a timeout re-sends the same create request; a caller that saved the threadId and re-runs initialization code on restart; frontend generating a fixed threadId instead of a UUID per conversation; race where two requests create the same threadId concurrently (the warn log 'Attempted to create duplicate thread' indicates this path).

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


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