alibaba/spring-ai-alibaba · warning · ResponseStatusException

Thread already exists:

Error message

Thread already exists: 

What it means

GraphThreadController.createThreadWithId rejects client-supplied thread IDs that already exist for the given graph and user. It first runs findThreadOrThrow as an existence probe; if the thread IS found, it throws a 400 BAD_REQUEST ResponseStatusException. This enforces thread IDs as client-chosen unique keys per (graph, user) pair.

Source

Thrown at spring-ai-alibaba-studio/src/main/java/com/alibaba/cloud/ai/agent/studio/controller/GraphThreadController.java:139

		}

		return response.threads().stream()
				.filter(s -> !s.threadId().startsWith(EVAL_SESSION_ID_PREFIX))
				.collect(toList());
	}

	@PostMapping("/graphs/{graphName}/users/{userId}/threads/{threadId}")
	public Thread createThreadWithId(
			@PathVariable String graphName,
			@PathVariable String userId,
			@PathVariable String threadId,
			@RequestBody(required = false) Map<String, Object> state) {
		validateGraphExists(graphName);
		String appName = toAppName(graphName);

		try {
			findThreadOrThrow(graphName, userId, threadId);
			throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Thread already exists: " + threadId);
		}
		catch (ResponseStatusException e) {
			if (e.getStatusCode() != HttpStatus.NOT_FOUND) {
				throw e;
			}
		}

		Map<String, Object> initialState = (state != null) ? state : Collections.emptyMap();
		Thread createdThread = threadService
				.createThread(appName, userId, new ConcurrentHashMap<>(initialState), threadId)
				.block();

		if (createdThread == null) {
			throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to create thread");
		}
		return createdThread;
	}

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Check existence first with GET /graphs/{graphName}/users/{userId}/threads/{threadId} and reuse or delete the existing thread
  2. Use the ID-less creation endpoint POST /graphs/{graphName}/users/{userId}/threads and let the service generate a unique threadId
  3. Generate a unique threadId client-side (e.g. UUID) instead of a fixed value
  4. If the existing thread is stale, DELETE it first, then recreate with the same ID
  5. Catch the 400 ResponseStatusException in your client and treat it as idempotent success if the thread content is acceptable

Example fix

// before
POST /graphs/agent/users/u1/threads/session-1   // 400 Thread already exists
// after
String threadId = UUID.randomUUID().toString();
POST /graphs/agent/users/u1/threads/" + threadId
Defensive patterns

Strategy: validation

Validate before calling

// check existence before creating
try {
  restTemplate.getForObject("/graphs/{g}/users/{u}/threads/{t}", Void.class, graph, user, threadId);
  return; // already exists
} catch (HttpClientErrorException.NotFound ignored) { }
// safe to create

Try / catch

try { create(...) } catch (HttpStatusCodeException e) { if (e.getStatusCode() == HttpStatus.BAD_REQUEST) { /* reuse existing thread */ } else throw e; }

Prevention

When it happens

Trigger: POST to /graphs/{graphName}/users/{userId}/threads/{threadId} (PUT-style create) with a threadId that was already created previously for the same graph and user; retrying a successful creation with the same ID; two concurrent creations of the same ID.

Common situations: Client retries after a network timeout when the first request actually succeeded; a UI generating deterministic thread IDs from session names; scripts re-running a setup step that creates threads with fixed IDs.

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/538cb1271068ac1a. Report an issue: GitHub.