spring-projects/spring-ai · error · IllegalStateException

unknown message type %s

Error message

unknown message type %s

What it means

CassandraChatMemoryRepository.getMessage() maps stored message-type rows to Spring AI Message objects. Only SYSTEM, USER and ASSISTANT rows are supported; TOOL rows are intentionally filtered out by the caller, and any other (unknown) type hits the default branch, which throws IllegalStateException. This protects against corrupted or forward-incompatible rows written by a newer schema/version.

Source

Thrown at memory-repositories/spring-ai-model-chat-memory-repository-cassandra/src/main/java/org/springframework/ai/chat/memory/repository/cassandra/CassandraChatMemoryRepository.java:240

			stmt = stmt.whereColumn(columnName).isEqualTo(QueryBuilder.bindMarker(columnName));
		}
		stmt = stmt.limit(QueryBuilder.bindMarker("legacy_limit"));
		return this.conf.session.prepare(stmt.build());
	}

	private @Nullable Message getMessage(UdtValue udt) {
		String content = Objects.requireNonNullElse(udt.getString(this.conf.messageUdtContentColumn), "");
		Map<String, Object> props = Map.of(CONVERSATION_TS, udt.getInstant(this.conf.messageUdtTimestampColumn));
		String type = udt.getString(this.conf.messageUdtTypeColumn);
		Assert.state(type != null, "message type shouldn't be null");
		return switch (MessageType.valueOf(type)) {
			case ASSISTANT -> AssistantMessage.builder().content(content).properties(props).build();
			case USER -> UserMessage.builder().text(content).metadata(props).build();
			case SYSTEM -> SystemMessage.builder().text(content).metadata(props).build();
			// this implementation doesn't support tool calls message persistence, so
			// TOOL rows are filtered out by the caller
			case TOOL -> null;
			default -> throw new IllegalStateException(String.format("unknown message type %s", type));
		};
	}

}

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Inspect the offending row with a CQL SELECT and fix or delete the row with the unexpected MESSAGE_TYPE
  2. Align the library version that reads the table with the version that wrote it
  3. Extend the switch to handle the new type if you control the code, or pre-filter unknown types in your query
  4. Delete corrupted rows and re-save the conversation history

Example fix

// before
Row badRow = rows with type 'FUNCTION';
repository.findByConversationId("conv1"); // throws IllegalStateException
// after
cql> DELETE FROM ai_chat_memory WHERE conversation_id = 'conv1' AND message_type = 'FUNCTION';
Defensive patterns

Strategy: validation

Validate before calling

List<Message> msgs = repo.findByConversationId(id);
if (msgs == null || msgs.stream().anyMatch(m -> m.getMessageType() == null)) { /* handle bad data */ }

Type guard

boolean isKnownType(String t) { return t != null && List.of("USER","ASSISTANT","SYSTEM","TOOL").contains(t); }

Try / catch

try { messages = repo.findByConversationId(id); }
catch (IllegalStateException e) { messages = List.of(); logger.warn("Skipping conversation with unknown message type", e); }

Prevention

When it happens

Trigger: Reading a conversation whose stored MESSAGE_TYPE column contains a value outside SYSTEM/USER/ASSISTANT/TOOL — e.g. a row written by a different or newer library version, or a manually inserted row with a typo'd type.

Common situations: Schema drift between library versions, manual CQL inserts into the messages table, or rows written by another tool sharing the same table/keyspace.

Related errors


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