Skip to content

Java vs Python for AI: polyglot architecture decisions

At a glance

Priority: P1 · Complexity: ⅖ · Est. time: 1 h · Phase: 4 · Prereqs: Spring AI tools & MCP, Spring AI agents, AI-native architecture You're done when: you can write a one-page ADR that assigns AI capabilities to Java or Python services with explicit contracts, and defend it against "rewrite everything in X" arguments.

Why it matters

Most large enterprises will not choose "Java or Python" — they will run both. Systems of record, transactions and security live in Java; experimentation, evals, ML tooling and much of the agent ecosystem live in Python. The architect's job is to draw the seams so that each side does what it's best at, contracts are explicit, and neither team becomes a bottleneck. In interviews this appears as "we're a Java shop and want AI features — what do you do?" and the strong answer is a boundary design, not a language preference.

Core concepts

Where each language is strong (as of September 2026)

Dimension Java 25 / Spring Boot 4 / Spring AI 2.0 Python 3.14 / LangGraph 1.x / Pydantic AI
Agent frameworks Spring AI (workflows, tools, MCP), LangChain4j, Embabel, ADK Java LangGraph (durable graphs), Pydantic AI, DSPy, OpenAI/Claude Agent SDKs, ADK Python 2.0, MS Agent Framework
Durable execution / HITL Roll your own or workflow engine LangGraph checkpointers/interrupts
Prompt/program optimisation, eval tooling Limited DSPy, Ragas, DeepEval, promptfoo, Inspect (Python-first)
ML / local inference / fine-tuning ONNX Runtime, DJL; model servers PyTorch, HF, vLLM, TRL — the native home
Enterprise integration Excellent: Spring Security, transactions, messaging, JPA, observability Good but more assembly required
Concurrency for I/O fan-out Virtual threads, structured concurrency (preview) asyncio; free-threaded 3.14t optional
Type safety / refactoring at scale Strong static types, mature tooling Improving (typing, ty/pyright), weaker guarantees
Time to prototype Slower, improving (Spring Initializr, compact source files) Fastest
Hiring pool for AI-specific skills Smaller Larger
Protocol support (MCP, A2A, AG-UI) MCP server/client native in Spring AI 2.0 All first-class

Decision framework: place each capability by five questions

  1. Where is the data and the transaction? Capabilities that must join, lock or commit alongside domain data belong in the domain's runtime (usually Java).
  2. Who owns the on-call? The team that runs it must be fluent in it.
  3. Does it need Python-only tooling? Fine-tuning, local model serving, DSPy optimisation, rapid eval iteration — Python.
  4. What's the state model? Long-running, human-in-the-loop, resumable → durable runtime (LangGraph); short, stateless → either.
  5. What's the blast radius/security posture? Tools that mutate systems of record should sit with the domain owners and their authorisation model.

Reference topology (the capstone)

flowchart TB
    subgraph Py[Python: experimentation & orchestration]
      ORCH[LangGraph orchestrator<br/>plan · checkpoints · HITL]
      EVAL[Eval harness & golden sets<br/>DSPy / Ragas / promptfoo]
      INF[Model serving / rerankers<br/>vLLM · ONNX]
    end
    subgraph Jv[Java: domain & governed services]
      MCP[Spring AI MCP tool server<br/>@McpTool @McpResource]
      RAG[Spring AI RAG service<br/>pgvector · tenant filters]
      DOM[Domain services & DBs]
    end
    GW[LLM gateway<br/>routing · keys · quotas · caching]
    ORCH -->|MCP Streamable HTTP + OAuth2| MCP
    ORCH -->|HTTP/gRPC| RAG
    RAG --> DOM
    MCP --> DOM
    ORCH --> GW
    RAG --> GW
    EVAL -.same golden sets.-> ORCH
    EVAL -.-> RAG
    ORCH -->|OTLP, traceparent| OBS[(OTel + LLM observability)]
    MCP --> OBS
    RAG --> OBS

Contracts that keep the seam healthy

Contract Shape Why
Tool interface MCP (JSON Schema for args/results), versioned; snapshot-tested tools/list Language-neutral; consumed by any agent runtime
Data schemas JSON Schema as the source of truth; generate Java records and Pydantic models No drift between runtimes
Deadlines & retries Propagated deadline; exactly one retry layer; idempotency keys on mutating tools Avoid retry storms and double writes
Identity End-user token propagated (OAuth token exchange), not shared service accounts Correct authorisation and audit
Observability W3C traceparent, common attribute names, shared dashboards One trace across runtimes
Evals One golden set and rubric, run against every implementation Comparable quality claims
Deployment Independent pipelines, semver on contracts, consumer-driven contract tests Teams release independently

Anti-corruption and cost of the seam

Each network hop adds latency (low single-digit ms in-cluster), serialisation, a failure mode and an operational surface. Don't split what changes together. A capability that is always changed by the same team in the same release should live in one service. Keep the number of cross-language contracts small and stable; prefer coarse-grained tools and a few service APIs over chatty interactions.

Three archetypes

Archetype Description Use when
Java-only Spring AI for everything (RAG, tools, workflows) Java-heavy org, simple stateless features, strict platform standardisation
Python-orchestrated, Java-tooled LangGraph/Pydantic AI orchestrates; Java exposes tools/RAG via MCP/HTTP Complex/durable agents plus rich existing Java domain services (the capstone)
Python-only FastAPI + agent framework; Java untouched Greenfield product with no Java dependencies, small team, fast experimentation

Migration and convergence

  • Start hybrid deliberately; record an ADR with revisit triggers (e.g., "revisit when Spring AI ships durable runtime X" or "when >3 teams need the same orchestrator").
  • Build the shared eval harness first — it lets you compare implementations objectively and move capabilities later without a quality debate.
  • Avoid mirror implementations of the same feature in both languages unless it's a time-boxed spike.

Resources

Resource Type Why this one Level Cost
Spring AI 2.0.0 GA announcement article What Spring AI covers natively today (MCP, agentic advisors) intermediate free
Spring AI MCP overview docs The Java side of the seam intermediate free
MCP specification docs Contract semantics, auth, statelessness advanced free
langchain-mcp-adapters code Python consumer of Java MCP servers intermediate free
Anthropic: Building effective agents article Shared vocabulary for deciding workflow vs agent intermediate free
awesome-spring-ai 💎 article Shows the breadth of the Spring AI community ecosystem for gap analysis intermediate free
Team Topologies article/book Team-boundary thinking that should drive service boundaries intermediate freemium
ADRs docs Template and practice for recording this decision beginner free

Hands-on lab

Goal (60 min): write and validate the polyglot ADR for your capstone.

  1. List capstone capabilities (orchestration, HITL, RAG retrieval, ingestion, shipment tools, pricing tools, evals, reranking, gateway).
  2. Score each against the five placement questions; assign Java/Python; note the contract.
  3. Draw the topology (above as a starting point) with hops and their latency budget.
  4. Write a one-page ADR: context, decision, alternatives (Java-only, Python-only), consequences, revisit triggers.
  5. Validate with a spike: measure the added latency of Python → Java MCP call vs in-process Python tool (e.g., 20 sequential calls) and record the number in the ADR.

Expected output: ADR markdown, topology diagram, and a measured hop-latency figure.

Questions

L1 — Recall

Q1. Name three capabilities that are usually better placed in Python and three in Java for an enterprise AI platform.
Answer

Python: durable agent orchestration (LangGraph), eval/optimisation tooling (DSPy, Ragas, promptfoo), local model serving/fine-tuning. Java: transactional domain tools with the company's authorisation model, RAG services close to the systems of record and their ACLs, high-concurrency governed APIs and gateways integrating with existing Spring infrastructure.

Q2. Which protocol do you use as the language-neutral tool contract, and which Spring AI artifact exposes it?
Answer

MCP (Model Context Protocol) over Streamable HTTP; Spring AI's spring-ai-starter-mcp-server-webmvc (or -webflux) with @McpTool/@McpResource annotations.

Q3. What is the cost of a cross-language seam?
Answer

Extra latency (network + serialisation), a new failure mode, auth/identity propagation work, contract versioning, two toolchains/CI pipelines and on-call skills. It's justified only by ownership, ecosystem or scaling benefits.

L2 — Apply

Q4. Your Python agent must cancel a booking through a Java service. List the safeguards across the boundary.
Answer

Destructive tool annotation and a HITL approval in the orchestrator before invocation; end-user token propagation and server-side authorisation (not model-supplied ids alone); idempotency key on the cancel call; deadline propagation and single retry ownership; audit log with run id and principal; tool-level rate limit; contract test for the schema; and a compensating action if a later step fails.

Q5. Keep Java records and Pydantic models in sync for a shared ShipmentRisk schema.
Answer

Make JSON Schema (or an OpenAPI component) the source of truth in a shared repo/package; generate Pydantic models (e.g., datamodel-code-generator) and Java records (e.g., jsonschema2pojo or hand-written with schema tests); add a CI check that validates sample payloads against both. Version the schema (semver, additive changes) and fail builds on breaking diffs.

Q6. Design a test that proves the Java MCP server still satisfies the Python orchestrator after a change.
Answer

Consumer-driven contract: the orchestrator team owns a pytest suite that starts the server container and asserts tools/list names/schemas (snapshot) and calls key tools with fixture data expecting shapes/status. The Java pipeline runs that suite (or a published snapshot) before release. Failing tests block breaking changes; additive changes pass.

L3 — Design & trade-offs

Q7. Java-only (Spring AI everywhere) vs Python-orchestrated + Java tools — decide for a claims-processing assistant with day-long approvals.
Answer

Day-long approvals require durable, resumable state and HITL — LangGraph provides checkpointing/interrupts out of the box; Spring AI does not. So: Python orchestrates; Java exposes claims/policy tools and RAG over policy documents (governed data, ACLs). Costs: an extra deployable and contract discipline. If the team cannot operate Python, alternative is Java + a workflow engine (Temporal/Camunda) with Spring AI for step logic — viable but you rebuild HITL patterns. Decide by team skills and time-to-value.

Q8. Where should the LLM gateway live and who owns it?
Answer

As a language-neutral platform service (LiteLLM-style proxy or vendor gateway) owned by the platform team: routing, keys, quotas, caching, guardrails and cost attribution for both Java and Python callers. Embedding these in each language's SDK duplicates logic and fragments governance. Both Spring AI and Python frameworks point their base URL at the gateway.

Q9. Should the RAG retrieval service be Java or Python?
Answer

Retrieval that enforces document ACLs, tenancy and lives beside the source systems favours Java (security integration, ops maturity) — especially on pgvector with existing Postgres operations. If retrieval quality iteration is heavy (rerankers, hybrid experiments, LLM-based query understanding) Python speeds up experimentation; a hybrid is common: Python prototypes, Java productionises behind a stable retrieval API and the shared eval set guarantees parity.

L4 — Staff-level ambiguity

Q10. A VP says 'standardise on one language for AI to reduce cost.' Respond.
Answer

Acknowledge the real cost (duplicate tooling, hiring, on-call) and reframe: standardise contracts, platforms and practices (MCP tools, gateway, observability, eval harness, security), and limit languages to two with clear roles rather than allowing per-team sprawl. Quantify: a rewrite of Python-native tooling (fine-tuning, evals) into Java or Java domain services into Python costs more and risks quality. Offer a decision record and a review date; propose metrics (lead time, incidents, cost per feature) to revisit the split with evidence.

Q11. Six months in, the Java tool team and the Python orchestrator team blame each other for latency and failures. What do you do?
Answer

Make the seam observable and shared: cross-language traces with traceparent, per-tool SLOs (latency/error) and a joint dashboard; agree on timeout/retry hierarchy and error taxonomy (retryable vs permanent, model-safe messages); run a blameless review on top incidents with trace evidence; introduce consumer-driven contract tests and a shared on-call escalation path; consider whether an org boundary (team topology) mismatches the technical boundary and adjust ownership. Treat it as a platform interface problem, not a people problem.

Real-world use cases

  • Logistics support copilot: LangGraph agent (Python) with Java MCP tools for bookings/tariffs and a Java RAG service over carrier terms.
  • Bank/insurer: Java retains regulated data services and audit; Python teams run evaluation and model-serving; a gateway enforces data-loss-prevention.
  • Retail: Python recommendation/ML services with Java checkout/pricing tools exposed to an assistant.
  • Internal developer assistant: Python agents (IDE/CLI) consuming Java platform tools (service catalog, deployment status) via MCP.

Pitfalls & anti-patterns

  • Language tribalism replacing capability-based placement.
  • Two implementations of the same agent "to compare" that never converge.
  • Chatty cross-language interfaces (dozens of hops per user request).
  • Shared service accounts across the seam, losing end-user identity.
  • No shared eval harness, so quality debates are anecdotal.
  • Retrying at every layer.

Checklist

  • I can state the five placement questions and apply them to a capability list
  • I wrote a polyglot ADR with revisit triggers
  • I measured the cross-language hop latency
  • I can defend the boundary against "rewrite in one language"
  • I answered all L3 questions out loud in < 3 min each