spring-projects/spring-ai · error · IllegalArgumentException

clients must not be empty

Error message

clients must not be empty

What it means

The compact constructor of AsyncToolListChangedSpecification validates the `clients` array: it must be non-null, non-empty, and contain no blank entries. This mirrors the sync variant; without at least one client name the async tool-list-changed specification would register against nothing and silently do nothing.

Source

Thrown at mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/changed/tool/AsyncToolListChangedSpecification.java:33

 */

package org.springframework.ai.mcp.annotation.method.changed.tool;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;

import io.modelcontextprotocol.spec.McpSchema;
import reactor.core.publisher.Mono;

public record AsyncToolListChangedSpecification(String[] clients,
		Function<List<McpSchema.Tool>, Mono<Void>> toolListChangeHandler) {

	public AsyncToolListChangedSpecification {
		Objects.requireNonNull(clients, "clients must not be null");
		if (clients.length == 0 || Arrays.stream(clients).map(String::trim).anyMatch(String::isEmpty)) {
			throw new IllegalArgumentException("clients must not be empty");
		}
		Objects.requireNonNull(toolListChangeHandler, "toolListChangeHandler must not be null");
	}

}

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Pass at least one real MCP client name: new AsyncToolListChangedSpecification(new String[]{"myClient"}, handler)
  2. Validate/sanitize the client-name source before constructing; filter blanks and fail early if the result is empty
  3. Check configuration/property binding so the clients list is actually populated at startup

Example fix

// before
AsyncToolListChangedSpecification spec =
    new AsyncToolListChangedSpecification(props.getClients().split(","), handler); // [""] when unset
// after
if (props.getClients() == null || props.getClients().isBlank())
    throw new IllegalStateException("At least one MCP client must be configured");
String[] clients = java.util.Arrays.stream(props.getClients().split(","))
    .map(String::trim).filter(s -> !s.isEmpty()).toArray(String[]::new);
AsyncToolListChangedSpecification spec = new AsyncToolListChangedSpecification(clients, handler);
Defensive patterns

Strategy: validation

Validate before calling

if (clients == null || clients.length == 0 || java.util.Arrays.stream(clients).map(String::trim).anyMatch(String::isEmpty)) { throw new IllegalArgumentException("clients must contain at least one non-blank name"); }

Type guard

static boolean validClients(String[] clients) { return clients != null && clients.length > 0 && java.util.Arrays.stream(clients).noneMatch(c -> c == null || c.trim().isEmpty()); }

Try / catch

try { new AsyncToolListChangedSpecification(clients, handler); } catch (IllegalArgumentException e) { log.error("Bad clients argument: {}", e.getMessage()); }

Prevention

When it happens

Trigger: Constructing new AsyncToolListChangedSpecification(new String[0], handler) or with only blank entries like new String[]{" ", ""} — either clients.length == 0 or the anyMatch(String::isEmpty) predicate throws IllegalArgumentException.

Common situations: Client names sourced from empty configuration properties, splitting an empty/unset comma-separated string into [""], or a wiring error where the array is built after an async lookup that returned no clients.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/f71e01534ea740d7. Report an issue: GitHub.