A from-scratch, runnable, tested implementation of every code example in Building AI Agents in
Java (2026 Edition) — the LangChain4j/Spring AI book in Building AI Agents in Java.pdf
/ .docx in this repo.
Two Maven modules:
| Module | Covers | Stack |
|---|---|---|
core/ |
Chapters 2–4, 6–12, 14 | Plain Java 21, LangChain4j 1.17.2 |
support-service/ |
Chapter 13 (AcmeSupport case study) | Spring Boot 4.1.0, Spring AI 2.0.0 |
Chapters 1 and 5 are conceptual/comparative and have no standalone examples of their own; their
code shows up inside the Chapter 6+ examples (Chapter 1's agent loop is Chapter 6's TaskAgent;
Chapter 5's AI Services pattern is used throughout core, and its Spring AI ChatClient
counterpart is support-service's RagConfig/SupportController).
- Java 21+ (developed and tested on Temurin 21.0.2)
- Maven 3.9+
- Nothing else to run the test suite — see "What runs offline" below.
- An
OPENAI_API_KEY(and/or a local Ollama daemon) only if you want to actually run themain()demos or the live-API tests against a real model.
mvn test # whole reactor: 57 tests, all green, zero external dependencies
mvn -pl core test # just the plain-Java chapters
mvn -pl support-service test # just the Spring Boot case study
# Run an individual chapter demo (needs OPENAI_API_KEY):
export OPENAI_API_KEY=sk-...
mvn -pl core exec:java -Dexec.mainClass=com.example.aiagents.ch06_first_agent.TaskAgentDemo
# Run the full AcmeSupport service (needs OPENAI_API_KEY):
mvn -pl support-service spring-boot:run
curl -s -X POST localhost:8080/support/message \
-H 'X-Customer-Id: cust-priya' -H 'Content-Type: application/json' \
-d '{"text":"Can I return a used blender after 40 days?"}'The design goal was a suite that is genuinely green with no API key and no network, not one that fakes success by skipping everything. Three techniques make that possible:
- A local, in-process embedding model (
AllMiniLmL6V2EmbeddingModel, Chapter 3/9) runs real ONNX inference with no network call, so Chapter 9's RAG ingestion/retrieval tests do real chunking, real vector embedding, and real cosine-similarity ranking — not mocks. - A hand-rolled
FakeChatModel(core'scom.example.aiagents.testkit.FakeChatModel, andsupport-service's test-only equivalent) implements the realChatModelinterface with scripted responses, including tool-call requests. This exercises the actual LangChain4j/ Spring AI tool-calling loop end to end — real tool method invocation, real memory, real structured-output parsing — with a deterministic, offline "model." This is exactly what Chapter 14.3 recommends: "mock the model in unit tests... reserve real-model evaluation for the dedicated eval suite." - A local HTTP stub server (
java.net.http's ownHttpServer) stands in for the OpenAI API inRawChatClientTest, so even the hand-rolled Chapter 4 HTTP client is tested against a real request/response cycle, not skipped.
A small number of tests are the genuine exception and self-skip via JUnit's Assumptions when
their precondition isn't met, rather than failing the build:
HelloLiveSmokeTest,OllamaHelloLiveSmokeTest,ConfigTest— skip withoutOPENAI_API_KEY/ a reachable Ollama daemon. Set the env var / start Ollama and they run for real.EmbeddingsDemoTest,RagIngestionRetrievalTest— skip only on the one platform combination where they can't: see the caveat below. They run for real on Linux (x86_64/arm64), Apple Silicon, and Windows.
AllMiniLmL6V2EmbeddingModel's tokenizer (ai.djl.huggingface:tokenizers:0.36.0) ships prebuilt
native binaries for linux-x86_64, linux-aarch64, osx-aarch64 (Apple Silicon), and
win-x86_64 — but not osx-x86_64 (Intel Mac). On that one platform, loading the model throws at
class-init time. core's testkit.LocalEmbeddingModelSupport detects this and skips the affected
tests with an explicit message rather than failing opaquely; the example code itself
(EmbeddingsDemo, RagDemo) is unchanged and correct — it just can't load its native dependency
on this specific host. This is an upstream DJL packaging gap, not a bug in the example.
| Chapter | Section(s) | Package |
|---|---|---|
| 2 | Setup, API keys, Hello/Ollama smoke tests, logging | ch02_setup |
| 3 | Embeddings | ch03_models |
| 4 | Raw HTTP client, framework chat, streaming, structured output | ch04_talking |
| 5 | AI Services pattern | ch05_frameworks |
| 6 | Tools, first agent, guardrails | ch06_first_agent |
| 7 | MCP client consumption | ch07_mcp |
| 8 | Prompt chaining, routing, parallelization, evaluator-optimizer | ch08_patterns |
| 9 | Conversation memory, RAG ingestion/retrieval | ch09_memory_rag |
| 10 | Multi-agent supervisor, typed hand-offs | ch10_multiagent |
| 11 | Retries/circuit breakers, virtual threads, idempotency | ch11_resilience |
| 12 | Token budgets, untrusted-content labeling, audit log | ch12_security |
| 14 | Property-based evaluation harness | ch14_eval |
| — | Offline ChatModel test double |
testkit |
Chapter 13 lives entirely in support-service (domain, tools, security, config, web
packages) — see its Javadoc-style comments for the section-by-section mapping to the AcmeSupport
case study.
The book targets a fast-moving 2026 ecosystem; a few APIs shifted slightly between when it was written and the versions actually on Maven Central today. Notable spots, each called out in a comment at the point of use:
- Spring AI 2.0's advisors are builder-based (
QuestionAnswerAdvisor.builder(store).build()) where the book shows constructors (new QuestionAnswerAdvisor(store)). - LangChain4j's
@P/tool reflection needs-parametersat compile time to see real parameter names; both modules' POMs enable it explicitly. - The MCP and agentic modules (
langchain4j-mcp,langchain4j-agentic) are still beta-versioned (1.17.2-beta27), exactly as the book describes and warns about.