{"record":{"id":"f986b59a78ebbfbd","repo":"alibaba/spring-ai-alibaba","slug":"unsupported-dto-type","errorCode":null,"errorMessage":"Unsupported DTO type: ","messagePattern":"Unsupported DTO type: ","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-studio/src/main/java/com/alibaba/cloud/ai/agent/studio/dto/messages/MessageDTO.java","lineNumber":130,"sourceCode":"\t\tpublic static Message toMessage(MessageDTO dto) {\n\t\t\tif (dto == null) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif (dto instanceof ToolRequestMessageDTO) {\n\t\t\t\treturn ((ToolRequestMessageDTO) dto).toAssistantMessage();\n\t\t\t}\n\t\t\telse if (dto instanceof AssistantMessageDTO) {\n\t\t\t\treturn ((AssistantMessageDTO) dto).toAssistantMessage();\n\t\t\t}\n\t\t\telse if (dto instanceof UserMessageDTO) {\n\t\t\t\treturn ((UserMessageDTO) dto).toUserMessage();\n\t\t\t}\n\t\t\telse if (dto instanceof ToolResponseMessageDTO) {\n\t\t\t\treturn ((ToolResponseMessageDTO) dto).toToolResponseMessage();\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\t\"Unsupported DTO type: \" + dto.getClass().getName()\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n}\n\n","sourceCodeStart":112,"sourceCodeEnd":138,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-studio/src/main/java/com/alibaba/cloud/ai/agent/studio/dto/messages/MessageDTO.java#L112-L138","documentation":"MessageDTOFactory.toMessage is the inverse of fromMessage: it maps known MessageDTO implementations (ToolRequestMessageDTO, AssistantMessageDTO, UserMessageDTO, ToolResponseMessageDTO) back into Spring AI Message objects. A MessageDTO of any other concrete type — notably ToolRequestConfirmMessageDTO, which exists in the Jackson @JsonSubTypes registry for human-in-the-loop confirmations but has no reverse mapping — triggers this IllegalArgumentException. The exception message includes the offending class name.","triggerScenarios":"Calling MessageDTOFactory.toMessage(dto) with a ToolRequestConfirmMessageDTO (e.g., DTOs deserialized from an interruption/pending-confirm payload), or with any custom MessageDTO implementation, then attempting to rebuild Spring AI Messages from the full DTO set (round-tripping thread values back into graph state).","commonSituations":"Persisting thread state as DTOs and later converting every DTO back to Messages when resuming a graph; human-in-the-loop flows that stored a tool-confirm DTO in the same map as normal messages; custom MessageDTO subclasses added by application code without updating the factory.","solutions":["Skip ToolRequestConfirmMessageDTO (and other non-reversible DTOs) when rebuilding Messages from DTO maps — filter them out before calling toMessage.","Store the original Message alongside the DTO if you need a faithful round trip through confirm/interruption flows.","If a reverse conversion is genuinely needed, add a branch in MessageDTOFactory.toMessage for the reported DTO type (e.g., convert ToolRequestConfirmMessageDTO back to ToolResponseMessage or drop it).","Never instantiate custom MessageDTO implementations for state that will be converted back via toMessage."],"exampleFix":"// before\nMap<String, Message> msgs = new HashMap<>();\ndtos.forEach((k, dto) -> msgs.put(k, MessageDTO.MessageDTOFactory.toMessage(dto))); // throws on ToolRequestConfirmMessageDTO\n\n// after\ndtos.forEach((k, dto) -> {\n    if (dto instanceof ToolRequestConfirmMessageDTO) return; // not convertible to a Message\n    msgs.put(k, MessageDTO.MessageDTOFactory.toMessage(dto));\n});","handlingStrategy":"type-guard","validationCode":"static boolean isConvertibleToMessage(MessageDTO d) {\n    return d instanceof ToolRequestMessageDTO || d instanceof AssistantMessageDTO || d instanceof UserMessageDTO || d instanceof ToolResponseMessageDTO;\n}","typeGuard":"static Message safeToMessage(MessageDTO d) {\n    if (isConvertibleToMessage(d)) return MessageDTO.MessageDTOFactory.toMessage(d);\n    return null; // e.g. ToolRequestConfirmMessageDTO has no reverse mapping\n}","tryCatchPattern":"try {\n    Message m = MessageDTO.MessageDTOFactory.toMessage(dto);\n} catch (IllegalArgumentException e) {\n    log.warn(\"Skipping non-reversible DTO: {}\", e.getMessage());\n}","preventionTips":["Skip ToolRequestConfirmMessageDTO entries when rebuilding Messages from DTO maps.","Never put human-in-the-loop confirm DTOs into maps that will be round-tripped to Messages.","Keep custom MessageDTO implementations out of state converted via the factory."],"tags":["dto","type-mapping","human-in-the-loop","messages"],"backgroundTag":"type-mismatch","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}