One Agent, Two Frameworks, Zero Lessons About Frameworks
I implemented the same agent with Spring AI and LangGraph — the boundary didn't move Part 6 findings of an experiment: building an LLM-powered support agent with deterministic boundaries. The companion repo contains the full code. "Spring AI or LangChain?" It's the first question everyone asks, and

I implemented the same agent with Spring AI and LangGraph — the boundary didn't move Part 6 findings of an experiment: building an LLM-powered support agent with deterministic boundaries. The companion repo contains the full code. "Spring AI or LangChain?" It's the first question everyone asks, and I answered it the expensive way: I built the same agent twice. The frameworks turned out to be the least interesting decision in the design. Here's what the comparison actually taught, which is not what I expected to write. In both implementations, the framework does exactly one thing: turn a customer message into a Classification — a typed record naming the action and carrying the order id. Everything after that point is identical: eligibility rules, risk-tier gate, approval queue. Same code, same tests, same behaviour. // dev/tonal/support/ai/SpringAiIntentClassifier.java public final class SpringAiIntentClassifier implements IntentClassifier { private final ChatClient chatClient; public SpringAiIntentClassifier(ChatClient.Builder chatClientBuilder) { this.chatClient = chatClientBuilder .defaultSystem(SYSTEM_PROMPT) .build(); } @Override public Classification classify(String customerMessage) { return chatClient.prompt() .user(customerMessage) .call() .entity(Classification.class); } } That's the entire integration surface — one class, one method. The LangGraph version differs only in mechanics: Concern Spring AI LangGraph Tool definition @Tool on methods @tool decorator Structured output .entity(Classification.class) response_format=Pydantic Orchestration loop Advisor chain ReAct graph Provider swap Starter dependency Model class flowchart LR subgraph S["Spring AI implementation"] direction LR S1["ChatClient prompt"] --> S2[".entity(Classification)"] end subgraph L["LangGraph implementation"] direction LR L1["ReAct agent"] --> L2["structured_response"] end S2 --> P["Classification record"] L2 --> P P --> G{"GatedActionService<br/>(unchanged)"} classDef step fill:#eef2f6,stroke:#8fa3b8,color:#24313f classDef shared fill:#ecf2ed,stroke:#93b39d,color:#3d5344 classDef decision fill:#f7f4ec,stroke:#b3a988,color:#24313f class S1,S2,L1,L2,P step class G decision style S fill:#f7f9fb,stroke:#c5d1dc,color:#24313f style L fill:#f7f9fb,stroke:#c5d1dc,color:#24313f Different vocabulary, same job. Neither framework knows or cares that downstream sits a refund policy engine that would reject half its suggestions. The repo contains an end-to-end test whose classifier is neither framework — it's a keyword fallback (which doubles as provider-outage insurance). Swap any implementation behind the port; the test doesn't change: @Test void classifiedRefundRequestLandsInHumanApprovalQueue() { Classification classification = classifier.classify("I want a refund for ORD-101, it never arrived"); var result = gate.propose( classification.action(), "refund %s — %s".formatted(classification.orderId(), classification.reason())); assertThat(result.outcome()) .isEqualTo(GatedActionService.Outcome.QUEUED_FOR_APPROVAL); } Spring AI, LangGraph, keyword matching — all three produce the same record, hit the same gate, land in the same queue. If your framework choice changes what your system does, that logic was in the wrong place to begin with. To be fair, they aren't interchangeable in every dimension. Spring AI keeps everything in one language and build; LangGraph gives you graph-shaped control flow that's nicer for complex multi-step reasoning — and costs you a second runtime. Spring AI's advisor model is thinner than LangGraph's node/edge composition; if the agent grows genuinely complex orchestration, that gap matters. Today's agent has exactly two steps, so it doesn't. MCP deserves a mention here too: both ecosystems speak the Model Context Protocol, meaning tools defined once can be consumed from either stack. More evidence that the durable decisions live at the protocol level, not inside any framework. Choosing between agent frameworks is a real decision — but it's a week-two decision about developer ergonomics, not a design decision about what your system may do. The things that made this system trustworthy were all decided before either framework was picked: which judgments go to the model, which facts stay in code, who approves consequential actions. Those survive every framework migration you'll ever do. Loan underwriting didn't change when scoring engines were swapped; clinical systems kept their prescription boundaries through three generations of decision support. Same rule every time. The boundary is the architecture. The framework is a dependency.
Key Takeaways
- •I implemented the same agent with Spring AI and LangGraph — the boundary didn't move Part 6 findings of an experiment: building an LLM-powered support agent with deterministic boundaries
- •This story was reported by Dev.to, covering developments in the dev space.
- •AI advancements continue to reshape industries — read the full article on Dev.to for complete coverage.
📖 Continue reading the full article:
Read Full Article on Dev.to →


