spring-projects/spring-ai · error · IllegalArgumentException

Unsupported tool message class:

Error message

Unsupported tool message class: 

What it means

createToolChatCompletionMessages(Message) only accepts ToolResponseMessage instances; any other class whose messageType is TOOL is rejected with IllegalArgumentException naming the actual class. It is dispatched from createChatCompletionMessages when MessageType == TOOL.

Source

Thrown at models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java:439

		return switch (message.getMessageType()) {
			case USER -> Stream.of(createUserChatCompletionMessage(message));
			case SYSTEM -> Stream.of(createSystemChatCompletionMessage(message));
			case ASSISTANT -> Stream.of(createAssistantChatCompletionMessage(message));
			case TOOL -> createToolChatCompletionMessages(message);
			default -> throw new IllegalStateException("Unknown message type: " + message.getMessageType());
		};
	}

	private Stream<ChatCompletionMessage> createToolChatCompletionMessages(Message message) {
		if (message instanceof ToolResponseMessage toolResponseMessage) {
			// @formatter:off
			return toolResponseMessage.getResponses()
				.stream()
				.map(this::createToolChatCompletionMessage);
			// @formatter:on
		}
		else {
			throw new IllegalArgumentException("Unsupported tool message class: " + message.getClass().getName());
		}
	}

	private ChatCompletionMessage createToolChatCompletionMessage(ToolResponseMessage.ToolResponse toolResponse) {
		return new ChatCompletionMessage(toolResponse.responseData(), ChatCompletionMessage.Role.TOOL,
				toolResponse.name(), null, toolResponse.id());
	}

	private ChatCompletionMessage createAssistantChatCompletionMessage(Message message) {
		if (message instanceof AssistantMessage assistantMessage) {
			List<ToolCall> toolCalls = null;

			if (!CollectionUtils.isEmpty(assistantMessage.getToolCalls())) {
				toolCalls = assistantMessage.getToolCalls().stream().map(this::mapToolCall).toList();
			}
			String content = assistantMessage.getText();
			return new ChatCompletionMessage(content, ChatCompletionMessage.Role.ASSISTANT, null, toolCalls, null);
		}

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Return tool results as Spring AI ToolResponseMessage with ToolResponse entries
  2. Wrap custom tool messages into ToolResponseMessage before calling the model
  3. Check that the message was not produced by a different model integration's API types

Example fix

// before
Message toolMsg = new MyToolMessage(results);
// after
Message toolMsg = new ToolResponseMessage(results.stream()
    .map(r -> new ToolResponseMessage.ToolResponse(r.id(), r.name(), r.output()))
    .toList());
Defensive patterns

Strategy: type-guard

Validate before calling

if (msg.getMessageType() == MessageType.TOOL && !(msg instanceof ToolResponseMessage)) {
    throw new IllegalArgumentException("tool messages must be ToolResponseMessage");
}

Type guard

boolean isToolResponse(Message m) {
    return m instanceof ToolResponseMessage;
}

Try / catch

try {
    model.call(new Prompt(messages));
} catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("Unsupported tool message class")) {
        logger.error("convert tool message: {}", e.getMessage());
    } else throw e;
}

Prevention

When it happens

Trigger: A Message with MessageType TOOL whose runtime class is not ToolResponseMessage (e.g., a custom ToolMessage implementation) reaches the model.

Common situations: Custom tool-result message types from other integrations reused with MistralAiChatModel, or adapters that only set the message type without using Spring AI's ToolResponseMessage.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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