alibaba/spring-ai-alibaba · error · IllegalArgumentException
GraphFlux done result map keys must be String
Error message
GraphFlux done result map keys must be String
What it means
NodeExecutor.copyStateMap copies the map supplied to GraphFlux/Data.done() into the typed graph state Map<String,Object>. Graph state keys must be Strings; if any key is a non-String (e.g. Integer, enum, raw key from another map), IllegalArgumentException 'GraphFlux done result map keys must be String' is thrown.
Source
Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/executor/NodeExecutor.java:811
return copyStateMap(resultMap);
}
}
}
Map<String, Object> state = new HashMap<>();
state.put(graphFluxStateKey(graphFlux), lastData);
return state;
}
private static String graphFluxStateKey(GraphFlux<?> graphFlux) {
return StringUtils.hasText(graphFlux.getKey()) ? graphFlux.getKey() : "result";
}
private static Map<String, Object> copyStateMap(Map<?, ?> resultMap) {
Map<String, Object> state = new HashMap<>();
for (Map.Entry<?, ?> entry : resultMap.entrySet()) {
if (!(entry.getKey() instanceof String key)) {
throw new IllegalArgumentException("GraphFlux done result map keys must be String");
}
state.put(key, entry.getValue());
}
return state;
}
/**
* Checks interruptAfter hook for streaming nodes using the pre-merge state.
* <p>
* This method must be called <strong>before</strong> the streaming state updates are
* merged into the {@link OverAllState} to keep semantics consistent with the
* non-streaming interruptAfter hook.
* @param context the graph runner context
* @param actionResult the streaming node action result (state delta) passed to interruptAfter
* @return interruption metadata if the hook triggers
*/
private Optional<InterruptionMetadata> interruptAfterForStreaming(GraphRunnerContext context,
Map<String, Object> actionResult) {View on GitHub (pinned to f82da0b50f)
Solutions
- Convert all keys to String before calling done: map.entrySet() -> Map.of(String.valueOf(k), v).
- If keys are enums or IDs, map them to their configured state property names (String) first.
- Add a pre-check validating the map is Map<String, ?> before passing it to Data.done().
Example fix
// before
Map<Integer, Object> byId = ...;
return Data.done(byId);
// after
Map<String, Object> state = byId.entrySet().stream()
.collect(Collectors.toMap(e -> String.valueOf(e.getKey()), Map.Entry::getValue));
return Data.done(state); Defensive patterns
Strategy: type-guard
Validate before calling
for (Map.Entry<?, ?> e : doneMap.entrySet()) {
if (!(e.getKey() instanceof String)) {
throw new IllegalStateException("done map key not a String: " + e.getKey());
}
} Type guard
boolean hasStringKeys(Map<?, ?> m) {
return m.keySet().stream().allMatch(String.class::isInstance);
} Try / catch
try {
state = copyStateMap(resultMap);
} catch (IllegalArgumentException e) {
throw new IllegalStateException("GraphFlux done result must use String keys", e);
} Prevention
- Build done maps as Map<String, Object> explicitly instead of reusing domain-keyed maps.
- Convert ID/enum keys to their state property names before calling Data.done().
- Add an assertion util that validates String keys on any map handed to the graph state.
When it happens
Trigger: Calling Data.done(map) / returning a GraphFlux done result whose map uses non-String keys — typically a Map<Integer,Object>, Map<Enum,Object>, or a map built from untyped upstream data (e.g. parsed structures with non-string keys).
Common situations: Reusing domain maps keyed by IDs/enums as state updates; JSON/YAML parsers producing maps with non-string keys; building the done-map programmatically with mixed key types.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- unknown component type: + componentType.getValue()
- extra tool param should be map
- AppTypeNotSupport
- INVALID_PARAMS
- WORKFLOW_CONFIG_INVALID
AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09).
Data as JSON: /api/errors/041b9794feab75c1.
Report an issue: GitHub.