{"record":{"id":"f71e01534ea740d7","repo":"spring-projects/spring-ai","slug":"clients-must-not-be-empty-f71e01","errorCode":null,"errorMessage":"clients must not be empty","messagePattern":"clients must not be empty","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/changed/tool/AsyncToolListChangedSpecification.java","lineNumber":33,"sourceCode":" */\n\npackage org.springframework.ai.mcp.annotation.method.changed.tool;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.function.Function;\n\nimport io.modelcontextprotocol.spec.McpSchema;\nimport reactor.core.publisher.Mono;\n\npublic record AsyncToolListChangedSpecification(String[] clients,\n\t\tFunction<List<McpSchema.Tool>, Mono<Void>> toolListChangeHandler) {\n\n\tpublic AsyncToolListChangedSpecification {\n\t\tObjects.requireNonNull(clients, \"clients must not be null\");\n\t\tif (clients.length == 0 || Arrays.stream(clients).map(String::trim).anyMatch(String::isEmpty)) {\n\t\t\tthrow new IllegalArgumentException(\"clients must not be empty\");\n\t\t}\n\t\tObjects.requireNonNull(toolListChangeHandler, \"toolListChangeHandler must not be null\");\n\t}\n\n}\n","sourceCodeStart":15,"sourceCodeEnd":39,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/changed/tool/AsyncToolListChangedSpecification.java#L15-L39","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass at least one real MCP client name: new AsyncToolListChangedSpecification(new String[]{\"myClient\"}, handler)","Validate/sanitize the client-name source before constructing; filter blanks and fail early if the result is empty","Check configuration/property binding so the clients list is actually populated at startup"],"exampleFix":"// before\nAsyncToolListChangedSpecification spec =\n    new AsyncToolListChangedSpecification(props.getClients().split(\",\"), handler); // [\"\"] when unset\n// after\nif (props.getClients() == null || props.getClients().isBlank())\n    throw new IllegalStateException(\"At least one MCP client must be configured\");\nString[] clients = java.util.Arrays.stream(props.getClients().split(\",\"))\n    .map(String::trim).filter(s -> !s.isEmpty()).toArray(String[]::new);\nAsyncToolListChangedSpecification spec = new AsyncToolListChangedSpecification(clients, handler);","handlingStrategy":"validation","validationCode":"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\"); }","typeGuard":"static boolean validClients(String[] clients) { return clients != null && clients.length > 0 && java.util.Arrays.stream(clients).noneMatch(c -> c == null || c.trim().isEmpty()); }","tryCatchPattern":"try { new AsyncToolListChangedSpecification(clients, handler); } catch (IllegalArgumentException e) { log.error(\"Bad clients argument: {}\", e.getMessage()); }","preventionTips":["Sanitize the client-name source (trim/filter) before constructing the specification","Verify the clients property is bound and non-empty at startup","Cover the registration path with a test for the empty-clients case"],"tags":["java","validation","mcp","empty-array"],"backgroundTag":"empty-required-field","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}