AI 资讯
Building a Production AI Agent in Spring Boot: The LLM Judge That Scores Your Agent (Part 8)
Last week I ran a demo of the agent for a colleague who was deciding whether to bet a feature on it. The approval gate from Part 7 worked exactly as designed. The agent searched, added to cart, asked for the address, and stopped at the confirmation link. My colleague nodded and asked one question: "OK, but is it actually good?" I did not have an answer. I had 31 passing tests from Part 6, which prove the agent is bug-free. I had a state machine, which proves it cannot place an order without a human. Neither of those proves the agent answers customers well. A bug-free agent can still tell a customer the shop ships within two days when the shipping partner takes five. No unit test catches that, because no unit test reads the answer. That is the gap this part closes. Part 7 ended with a promise: the next part would build an evaluation harness, so "is it good" stops being a feeling and becomes a score. This is that part. I built an LLM-as-a-judge harness for the same e-commerce agent as Parts 1 through 7: same nine tools, same supervisor, same memory. It runs 40 real conversations from production logs against five metrics every night and prints a score for each one. The first run was uncomfortable, and that is exactly why it exists. I am a Senior Software Engineer II at BS23 in Dhaka, and I have been building production AI agents with Spring Boot and Spring AI for over a year. Everything below is the harness as I actually run it. The Difference Between Tested and Good Part 6 tested the agent without an LLM: 31 tests, zero model calls, asserting on tool calls, services, and the order state machine. That suite answers "did the agent call the right tool, in the right order, with the right arguments?" It cannot answer "was the answer right?" because you cannot write an assertion for an LLM's wording. Evaluation is a different layer. You cannot assert on the answer, but you can judge it, and you can use an LLM to do the judging. Spring AI documents this pattern in its LLM-as
AI 资讯
Spring Boot For Beginner
🚀 Building a REST API with Java Spring Boot: A Practical Beginner’s Guide If you're coming from Java and want to move into backend development, Spring Boot is one of the best frameworks to learn. It removes a lot of the boilerplate traditionally associated with Spring and makes it surprisingly easy to build production-ready REST APIs. In this article, we'll build a simple Blog REST API using: ☕ Java 🌱 Spring Boot 🌐 Spring Web 🗄️ Spring Data JPA 🐘 PostgreSQL 📦 Maven 🧪 Postman By the end, we'll have an API that can: Create a blog post Get all blog posts Get a post by ID Update a post Delete a post 1. What is Spring Boot? Spring Boot is a framework built on top of the Spring Framework that makes it easier to create Java applications. Without Spring Boot, you often need to configure many things manually. Spring Boot gives us: Auto-configuration Embedded servers Starter dependencies Production-ready features Easy REST API development A simple Spring Boot application can be started with: @SpringBootApplication public class BlogApplication { public static void main ( String [] args ) { SpringApplication . run ( BlogApplication . class , args ); } } That's enough to start our application. 2. Create the Spring Boot Project The easiest way to create a Spring Boot project is through Spring Initializr . Choose: Project: Maven Language: Java Spring Boot: Latest stable version Packaging: Jar Java: 17+ Add these dependencies: Spring Web Spring Data JPA PostgreSQL Driver Validation Lombok Your project structure will look something like: src └── main └── java └── com.example.blog ├── BlogApplication.java ├── controller ├── service ├── repository ├── entity └── dto This separation will become important as our application grows. 3. Create the Blog Entity Let's create a simple BlogPost entity. @Entity @Table ( name = "blog_posts" ) public class BlogPost { @Id @GeneratedValue ( strategy = GenerationType . IDENTITY ) private Long id ; @NotBlank private String title ; @NotBlank @Column (
AI 资讯
Java Spring Boot Logging: Log Levels, Logback, JSON Logs & Production Best Practices
Production-Grade Logging in Spring Boot: A Complete Guide to Logging Levels, Files, JSON Logs, Correlation IDs, and Best Practices Logging is one of the most important parts of a production backend system. When everything works, you may not think much about logs. But when production starts returning 500 errors at 2 AM, a customer reports that an API is failing, or a payment request behaves unexpectedly, logs become one of your most important debugging tools. Poor logging makes production debugging painful. Good logging helps you answer: What happened? When did it happen? Which user triggered it? Which request caused it? Which service handled it? How long did it take? What failed? Why did it fail? What should we investigate next? In this article, we will build a production-grade logging strategy for a Java Spring Boot application . 1. What Does Production-Grade Logging Mean? Production-grade logging is not simply: log . info ( "User created" ); Production logging should be: Structured Searchable Consistent Secure Configurable Environment-aware Correlated across requests Useful during debugging Suitable for monitoring and alerting A good logging architecture might look like this: Spring Boot Application | v Logback | +---- application.log | +---- error.log | +---- audit.log | +---- access.log | v Log Aggregation | +---- ELK +---- Grafana Loki +---- CloudWatch +---- Datadog +---- Splunk The goal is not to log everything. The goal is to log the right information at the right level . 2. Understanding Log Levels Spring Boot uses SLF4J as the logging abstraction and commonly uses Logback as the underlying logging implementation. The most common log levels are: TRACE DEBUG INFO WARN ERROR The order represents increasing severity. TRACE TRACE is the most detailed logging level. Example: log . trace ( "Entering calculateInvoice() with customerId={}" , customerId ); Use TRACE for very detailed diagnostic information. Usually: Production: OFF Development: Sometimes ON Debugging
AI 资讯
I Built a Self-Hosted AI Support Widget with Spring Boot (No Monthly SaaS Fees)
Every new SaaS seems to embed ChatGPT these days. Most AI support solutions rely on third-party platforms, monthly subscriptions, and vendor lock-in. While they're great products, I wanted something different. I wanted complete ownership. I wanted to deploy everything on my own server, use my own OpenAI API key, customize every part of the experience, and embed the widget into any website with a single script tag. So I built my own self-hosted AI support widget using Spring Boot and Vanilla JavaScript. Why I Built It When building small products and websites, I realized that customer support quickly becomes a problem. Users have questions about pricing, features, returns, or simply get stuck. Most developers solve this by integrating services like Intercom, Crisp, or Tidio. Those platforms are excellent, but they also mean: Monthly subscription costs Vendor lock-in Customer conversations stored on third-party platforms Limited customization Another external dependency I wanted something that developers could completely own. The Goal The goal was simple. Build an AI-powered customer support widget that developers can deploy on their own server and integrate into any website in less than a minute. The widget should: Answer customer questions using AI Learn from a custom knowledge base Match the company's branding Store conversation history Allow human handoff Be easy to deploy Require only one script tag to embed Technology Stack Java 17 Spring Boot 3 Spring Security Spring Data JPA Thymeleaf Vanilla JavaScript H2 Database (MySQL supported) OpenAI API Architecture The overall architecture is intentionally simple. Visitor │ ▼ AI Chat Widget (Vanilla JavaScript) │ ▼ Spring Boot REST API │ ▼ OpenAI API │ ▼ Database (H2 / MySQL) Keeping the frontend framework-free makes the widget lightweight and easy to embed into virtually any website. One-Line Integration Adding the widget to a website only requires a single script. <script src="/widget/widget.js" data-api-base=""></sc
AI 资讯
I used Spring Boot daily but never really understood what happened after pressing Enter in Postman.
Most of us use Spring Boot every day. We create a @RestController, run the application, hit an endpoint from Postman, and get a response. But have you ever wondered what actually happens between clicking "Send" in Postman and your controller method executing? When I started digging into Spring internals, I realized there are several layers working together before my controller is even called. Here's the high-level request flow: Postman │ ▼ Operating System │ ▼ Embedded Tomcat │ ▼ Servlet Filter Chain │ ▼ Spring Security (JWT) │ ▼ DispatcherServlet │ ▼ Controller │ ▼ Service │ ▼ Repository │ ▼ Database What surprised me? One thing I misunderstood for a long time was thinking that the request directly reaches my controller. In reality: The Operating System first routes the request to the application listening on the target port (for example, 8080). Embedded Tomcat accepts the connection. The request passes through the Servlet Filter Chain. Spring Security validates the JWT (if security is enabled). Only after successful authentication does the request reach Spring MVC's DispatcherServlet, which finds the correct controller. This means your controller only executes after several infrastructure components have already processed the request. Key Takeaway Understanding this request flow makes Spring Boot feel much less "magical." Instead of memorizing annotations, you begin to understand why they work. In the next post, I'll explain how Spring Boot starts Embedded Tomcat automatically before the first request even arrives.
AI 资讯
7 Production Issues Every Spring Boot Developer Should Learn Before Becoming Senior
After working on enterprise applications and distributed microservices, I have realized that the biggest challenges rarely come from writing business logic. They come from handling production traffic, failures, concurrency, and unexpected edge cases. Here are seven lessons that every Spring Boot developer should know before calling themselves a senior engineer. 1. Never Assume an API Will Be Called Only Once One of the most common mistakes is assuming a client sends exactly one request. In reality: Users refresh the page. Mobile apps retry automatically. API gateways retry requests. Kafka consumers may reprocess events. Network failures cause duplicate submissions. If your endpoint creates an order, payment, or booking every time it receives a request, duplicates are almost guaranteed. Better Approach Design APIs to be idempotent . For example: Use an Idempotency-Key. Store processed request IDs. Ignore duplicate requests safely. Production systems should always expect duplicate requests. 2. Database Transactions Are Not Enough Many developers believe this solves everything: @Transactional public void createOrder () { ... } It doesn't. A transaction protects changes inside a single database . It does not protect: Kafka publishing Email sending External REST APIs Redis updates File uploads If your database commits successfully but Kafka publishing fails, your system is already inconsistent. Better Approach Use patterns such as: Transactional Outbox Saga Pattern Event-driven architecture Retry with dead-letter queues 3. Don't Trust External APIs Every external service will eventually fail. Your payment provider. Your authentication service. Your notification service. Even your own internal microservices. Never assume another service is always available. Add Protection Timeouts Retries Circuit Breakers Fallback logic Monitoring Failing fast is usually better than waiting forever. 4. Logging Is More Valuable Than You Think When production goes down, nobody asks: "Was th
AI 资讯
Mapping Strategies Without the Magic (Chapter 4)
At the end of the previous chapter, I teased that we were about to dive straight into the heavy machinery of DAO implementation - hooking up Spring Data JPA and Hibernate under the hood. If you look at our original roadmap, concrete implementation was supposed to be right here. But after laying down our domain models, data contracts, and reading some of your feedback, I realized that we need to address an unspoken architectural trap first: data mapping and transformation . Most teams blindly adopt automated tools - whether runtime reflection wrappers or compile-time generators like MapStruct - until an architectural mismatch or production incident breaks their service. Before we wire up our database infrastructure, let’s see why we chose to completely bypass mapping "magic" in favor of pure, explicit Java transformers. And yes, some will say that writing explicit transformers is boilerplate - why write it manually when we can just use an annotation? The answer is simple: we’re not building a simple CRUD application that gets thrown to a support team and forgotten. We’re building an enterprise-ready microservice, built for deployment in Kubernetes, integrated with Kafka, Redis, and multi-tenant authorization layers - a product designed to be actively developed and maintained over years, not weeks. This is where the real value shines: spending a little more time writing explicit transformers - as I call them, rather than standard Mappers. I call them transformers because they actively reshape data. A "mapping" usually implies just copying a field from ClassA to ClassB (whether with the same or a different name). We aren't doing that here because at an enterprise level, field names, types, and structural representations will diverge significantly between your database entities, domain models, and external DTOs. The Architectural Trap Across my 11 years in Enterprise Java, I've seen team after team reach for automated mappers to "save time." To understand why we banned
AI 资讯
Article: Virtual Threads After JDK 24: What Changed for Production Java
JDK 24 removed the monitor-related carrier-thread pinning that stalled Netflix and similar teams on Java 21. What has replaced it on JDK 25 LTS is downstream-resource saturation: The bottleneck moved and now demands explicit bounding in application code. This article maps the failure modes that surface after virtual-thread adoption and gives a practical sequence backed by a public benchmark. By Sandeep Bharadwaj
AI 资讯
Spring AI Token Usage: Measure Cost Before You Pick a Model — LLM Cost Control 1/4
Cutting LLM costs in Spring AI starts with two choices: which model answers a request, and what defaults your ChatClient adds to every one it sends. Neither is worth changing until you can see where the tokens go. That is why this article starts with measurement. This is Part 1 of four, and it covers the first three of ten cost drivers. Driver #0 tells you where the money actually goes; #1 and #2 are the two decisions that shape every request your application sends. The remaining seven attach to what you build here. A note on the numbers: where a price ratio matters for the argument (input vs. output, cache read vs. write), this series quotes real July 2026 list prices with a link. All other examples use a flat rate of $1 per million input tokens, so you can redo the calculation with your own provider's price sheet. You should do that, because these prices change every few months. Driver #0 — Spring AI observability measurement: you cannot cut what you cannot see Provider invoices and usage dashboards usually show your spending by model and by token type — input, output, and cached. That is useful, but it is not enough. The numbers cannot tell you which feature, client, or advisor inside your application was responsible for that usage. Spring AI integrates with Spring Boot's Micrometer-based observability to fill this gap. Its core AI components automatically emit that data. ChatModel , EmbeddingModel , and ImageModel implementations (support varies by provider) publish model-level observations, including token usage where available. ChatClient (including advisors) and VectorStore report execution observations and traces rather than token usage metrics. Each metric includes built-in tags, such as the model name and token type. These tags separate models and providers, but not callers: every request to the same model carries the same tag values, so they cannot tell two features apart on their own. Spring AI marks tags as low- or high-cardinality: low-cardinality tags
AI 资讯
How to Reduce LLM Costs in Spring AI 2.0: 10 Practical Controls
Spring AI's defaults are built for a fast start; they do not guarantee a low monthly cost. Shipping an LLM feature is easy — making it cost-efficient is not. This series shows the spots where money leaks, along with the control that closes each one. Spring AI 2.0 reached GA on 12 June 2026 . It needs Spring Boot 4 , moves the tool-calling loop out of the ChatModel , adds tool search, and extends structured outputs. Tool search and the extended structured-output controls point in the same direction: they determine how many tokens your application sends and receives. The bill grows quietly. A chatbot with a 2,000-token system prompt, run 100,000 times a month, sends 200 million tokens of the same text. At an example rate of $1 per million input tokens, that is $200 a month — before a single user message. Then add conversation history, which is sent in full on every turn. Add retrieved RAG documents and the JSON schema of every registered tool. The input side can grow 10× with no change in traffic at all. Output tokens cost several times more per token than input tokens, and reasoning models bill their hidden "thinking" as output too. The provider sets the prices. The framework gives you controls that can reduce the number of tokens you pay for. This series works through ten cost drivers, numbered #0 to #9. Each one is a place where tokens repeat or grow without anyone deciding they should, and each comes with the Spring AI control that cuts it. They are spread across four parts. Part 1 is live. Parts 2 to 4 follow in August 2026. Part 1 — Token Usage: Measure Cost Before You Pick a Model (Drivers #0–#2) Provider dashboards show what you spent, but not which feature spent it. Spring AI's observability closes that gap, and from there you can match each model to its task and stop features from carrying defaults they never needed. Part 2 — Prompt Caching and Chat Memory: Where the Tokens Go (Drivers #3–#5) This part covers limiting response length, bounding how much conve
AI 资讯
Rod Johnson Is Back - and He's Bringing AI Agents to Java
If you have written enterprise Java in the last 20 years, you know the name Rod Johnson. He created Spring Framework back in 2003 - the thing that made Java dependency injection feel natural instead of like wrestling XML. Spring basically rewrote how enterprise Java works. Johnson stepped away from active Spring development years ago. But in early 2026, he returned - and he did not come back to build another IoC container. He built Embabel. An AI agent framework for the JVM. And it works nothing like Spring AI or LangChain4j. I have been running AI agents on my own VPS for months. Hermes Agent, Claude Code, custom MCP servers - the works. So when I heard Rod Johnson was back with a Java AI framework, I paid attention. Here is what I found. Spring AI and LangChain4j Are Great - but They Solve a Different Problem Over the last year, most Java developers entering AI have gravitated toward two frameworks: Spring AI - brings LLM integration into the Spring ecosystem LangChain4j - a Java port of LangChain's agent/tool patterns Both are excellent at what they do. You can build chatbots, RAG pipelines, tool-calling assistants, and AI-powered APIs in a few lines of code. But both treat the LLM as the center of the application. You send a prompt. The model responds. Maybe it calls a tool. Then it responds again. For question-answering or chat interfaces, that is fine. But what if you want the system to: Create a multi-step plan before taking any action Run for 10 minutes, not 10 seconds Check its own work and retry if it failed Coordinate multiple agents working on different parts of a problem This is where the chatbot pattern breaks down. And this is exactly what Embabel targets. What Is Embabel? Embabel (pronounced em-BAY-bel) is a framework for building goal-oriented AI agents on the JVM. It is written in Kotlin and works naturally from Java. It sits on top of Spring AI - Johnson described the relationship as "Spring AI is to Embabel as the Servlet API is to Spring MVC" [
AI 资讯
Stop Begging Your LLM for Valid JSON: Self-Correcting Structured Output in Spring AI 2.0
Every developer who has worked with LLMs has been there. You ask the model for JSON. You describe the schema. You say "please only respond with valid JSON." And sometimes, it still breaks. Your application crashes because the model returned a string where you expected an integer. Or it wrapped the JSON in markdown code blocks. Or it omitted a required field. Spring AI 2.0 has a solution that treats this like a real engineering problem instead of a prayer. The Problem When you use structured output in Spring AI, the workflow goes like this: You define a Java type (a record, class, or enum) Spring AI generates a JSON schema from that type The schema gets appended to the prompt sent to the LLM The model returns a response Spring AI attempts to deserialize the response into your type This works well with frontier models like Claude and GPT-4. But smaller open-source models, like Llama 3.2 1B running locally via Ollama, fail more often. They might return null for a primitive field, omit required fields, or produce malformed JSON. When it fails, you get a deserialization exception. Your endpoint returns a 500 error. Spring AI provides no built-in recovery mechanism. The Old Approach: Hope Consider a conference talk submission system. Speakers submit messy, unstructured abstracts. You want to extract structured data: public record TalkSubmission ( String title , String abstractText , Level level , // BEGINNER, INTERMEDIATE, ADVANCED Track track , int duration , List < String > tags , String speakerHandle ) {} Here is what the basic typed response looks like: @PostMapping ( "/typed" ) public TalkSubmission typed ( @RequestBody String rawSubmission ) { return chatClient . prompt () . system ( systemPrompt ) . user ( spec -> spec . text ( "Extract the talk submission: {submission}" ) . param ( "submission" , rawSubmission )) . call () . entity ( TalkSubmission . class ); } You define your type. Spring AI generates the schema and appends it to the prompt. The model gets the in
AI 资讯
Kiponos Java SDK 5.0 What’s New — Developer Guide
Kiponos Java SDK 5.0 What’s New — Developer Guide This is the technical companion to the 5.0 milestone announcement: what changed, how modes behave, how to read config with the Folder API, and how to upgrade cleanly. Version 5.0.0.260710 Maven group io.kiponos Artifacts sdk-boot-3 (recommended), sdk-boot-2 (legacy) Released 2026-07-12 (Maven Central) Happy product story: SDK 5.0 milestone post . 1. Summary for busy engineers 5.0 productizes client reliability using a classic state pattern behind a stable facade: Mode When Config reads Mutations / hooks Notes Ready Connected to hub Live in-memory tree Full Production happy path Offline Disconnected but LKG available Last Known Good (read-only) No-op / ignored Survives hub blips without inventing values Safe Fail-closed Empty / null-safe No-op Diagnostic dumps must not overwrite LKG Public entry remains: Kiponos kiponos = Kiponos . createForCurrentTeam (); You do not receive mode instances as the API surface. Modes switch internally. Query with: kiponos . getCurrentMode (); kiponos . isReadyMode (); kiponos . isOfflineMode (); kiponos . isSafeMode (); 2. Install Gradle — Boot 3 repositories { mavenCentral () } dependencies { implementation 'io.kiponos:sdk-boot-3:5.0.0.260710' } Gradle — Boot 2 implementation 'io.kiponos:sdk-boot-2:5.0.0.260710' Runtime inputs Input Mechanism Identity env KIPONOS_ID Access env KIPONOS_ACCESS Profile / tree slice JVM -Dkiponos="['App']['1.0.0']['dev']['base']" Tokens and profile come from the Kiponos Connect screen for your team. sdk-common is not a separate app dependency for consumers — boot jars include shared classes (fat-jar pattern). 3. Architecture (state pattern) Application code │ ▼ Kiponos / KiponosBase ◄── stable facade (one reference for app lifetime) │ ▼ volatile SdkState ├── ReadyMode* → live WebSocket + full Folder ops ├── OfflineMode* → LKG reads only └── SafeMode* → fail-closed + safe diagnostic dump Design rule: never return Ready/Offline/Safe objects to callers. Retur
AI 资讯
Building an AI Agent System with the ReACT Pattern in Java
From answering questions to solving problems — Phase 6 of the Jarvis AI Platform After Phase 5, Jarvis could hear, speak, remember conversations, retrieve documents, and use tools. But every interaction was still limited to a single request and a single response. You: "What's the weather in Kathmandu?" Whisper ↓ AiOrchestrator ↓ WeatherTool ↓ Text-to-Speech Jarvis: "It is 22°C and clear." That works well for simple questions. It completely breaks down when a task requires multiple decisions. The Limitation of Single-Turn AI Imagine asking: Research the top 3 Java AI frameworks, compare them, and summarize the findings. A traditional chatbot usually replies: I don't have enough information to research that. The problem isn't intelligence. The problem is planning. To answer properly, the AI must: Search for Java AI frameworks Search for comparisons Gather information Analyze results Produce a summary That requires multiple tool calls and reasoning between each one. This is exactly what AI agents are designed to do. What Is the ReACT Pattern? ReACT stands for: Reason + Act Instead of generating one response, the AI repeatedly performs a reasoning loop. THINK ↓ ACT ↓ OBSERVE ↓ THINK ↓ ACT ↓ OBSERVE ↓ FINAL ANSWER Example: THOUGHT: I should search for Java AI frameworks. ACTION: search INPUT: Java AI frameworks 2026 ↓ OBSERVATION: Spring AI LangChain4j Semantic Kernel ↓ THOUGHT: Now I need comparison data. ↓ ACTION: search INPUT: Spring AI vs LangChain4j ↓ FINAL ANSWER Instead of guessing everything up front, the AI gathers information step by step before producing the final response. The Biggest Architectural Decision The most important design decision of Phase 6 was not modifying the existing chat pipeline . Instead of turning AiOrchestrator into a giant class responsible for both chat and agents, agents became a completely separate orchestration layer. ❌ Wrong AiOrchestrator ↓ Single Chat ↓ Agent Logic ↓ Tool Logic ↓ Everything Mixed Together ✅ Correct AgentController
AI 资讯
Article: Scaling Java-Based Real-Time Systems: The Hidden Tradeoffs of Event-Driven Design
Event-driven architecture promises scalability, but in Java-based real-time systems the tradeoffs only surface in production. Drawing on a Java/Kafka contact center platform handling 80k BHCC across 10k agents, this article details where the design breaks down—state management, partition limits, deduplication, JVM tuning, cascading consumer failures—and the Redis-backed patterns that fixed each. By Sagar Deepak Joshi
AI 资讯
🗄️ The JPA Enum Default Quietly Corrupts Your Data
You add an enum to an entity, slap @Enumerated on it, and move on. Five seconds. It is the kind of decision nobody writes a design doc for. Then six months later a row comes back as SHIPPED when it was PAID , no exception was thrown, no query failed, and you spend an afternoon learning that the default you never thought about has been silently rewriting history. Here is the order lifecycle we will use the whole way through: public enum OrderStatus { PENDING , PAID , SHIPPED , DELIVERED } Five ways to store it. They are not equivalent, and the gap between them only shows up under change. @Enumerated(ORDINAL): store the position This is the default. Leave the annotation bare and JPA stores the enum's ordinal, its index in the declaration order. @Enumerated ( EnumType . ORDINAL ) private OrderStatus status ; PENDING is 0, PAID is 1, SHIPPED is 2, DELIVERED is 3. The column is a tidy little smallint . Everything works. Until someone needs a new status and adds it where it reads well: public enum OrderStatus { PENDING , PAID , CANCELLED , // inserted here SHIPPED , DELIVERED } CANCELLED is now 2. SHIPPED is 3. DELIVERED is 4. Every row written before this change still holds the old integer, so every order that was SHIPPED (2) now reads back as CANCELLED . The database is correct. Your data is wrong. And nothing told you. If you are stuck with ORDINAL on a legacy schema, pin it with a test that fails the build the moment someone reorders: @Test void ordinalsAreFrozen () { assertEquals ( 0 , OrderStatus . PENDING . ordinal ()); assertEquals ( 1 , OrderStatus . PAID . ordinal ()); assertEquals ( 2 , OrderStatus . SHIPPED . ordinal ()); assertEquals ( 3 , OrderStatus . DELIVERED . ordinal ()); } New constants may only be appended. The test turns an invisible runtime corruption into a loud compile-time-ish failure. It is a guardrail, not a fix. @Enumerated(STRING): store the name Store the constant name instead of its position. @Enumerated ( EnumType . STRING ) private OrderS
AI 资讯
Building a Tool Engine with Spring AI — How We Gave Jarvis the Ability to Act in the World
From knowing to doing — Phase 4 of the Jarvis AI Platform The Problem with Knowledge-Only AI After Phase 3, Jarvis could remember you across sessions and search your documents. But it still had a fundamental limitation. You: "What is the weather in Kathmandu right now?" Jarvis: "I don't have access to real-time weather data." You: "What is 2847 × 391?" Jarvis: "The answer is approximately 1.1 million." ← WRONG An AI that only knows things from training data is useful. An AI that can do things is transformative. That is what Phase 4 built. What Is a Tool Engine? A tool engine gives the AI model the ability to call real functions during a conversation. The flow looks like this: User: "What is the weather in Kathmandu?" ↓ AI Model ↓ "I should call WeatherTool" ↓ WeatherTool.getWeather("Kathmandu") ↓ "22°C, Clear sky, Humidity: 45%" ↓ AI Model ↓ "The weather in Kathmandu is 22°C and clear." The key insight: the AI decides when to call a tool and with what input . We don't hardcode "if user asks about weather, call WeatherTool." The model figures that out from the tool descriptions we provide. The Architecture Decision The most important architectural decision in Phase 4 was the package structure. ai . jarvis . tools / ├── JarvisTool . java ← marker interface ( root ) ├── ToolRegistry . java ← manages all tools ( root ) ├── builtin / ← built - in tools │ ├── DateTimeTool . java │ ├── CalculatorTool . java │ ├── WeatherTool . java │ └── WebSearchTool . java └── mcp / ← MCP protocol └── McpServerConfig . java Why not put tools inside ai/ ? The ai/ package handles HOW Jarvis talks to AI models. Tools define WHAT Jarvis can do. These are fundamentally different responsibilities. Mixing them would mean every new tool requires changes to AI infrastructure code. Keeping them, separate means adding a new tool requires exactly one file. The JarvisTool Pattern Every tool in Jarvis implements one interface. /** * Marker interface for all Jarvis tools. * Spring auto-discovers all @C
AI 资讯
7 Spring Boot Annotations Every Beginner Should Know
When I first started learning Spring Boot, I was overwhelmed by annotations. Every file seemed to have symbols starting with @ . @SpringBootApplication @RestController @Service @Autowired At first, I treated them like magic spells. I copied them from tutorials and hoped everything would work. Eventually, I realized that understanding a few key annotations made Spring Boot much less intimidating. If you're just starting your Spring Boot journey, these are the annotations I believe you should understand first. 1. @SpringBootApplication This is usually the first annotation you'll see in a Spring Boot project. @SpringBootApplication public class DemoApplication { public static void main ( String [] args ) { SpringApplication . run ( DemoApplication . class , args ); } } Think of it as the starting point of your application . When Spring Boot sees this annotation, it knows: Where the application begins Which components need to be scanned Which configurations should be loaded Without it, your Spring Boot application won't know how to start properly. 2. @RestController If you're building REST APIs, you'll use this annotation frequently. @RestController public class HelloController { @GetMapping ( "/hello" ) public String hello () { return "Hello, World!" ; } } A class marked with @RestController tells Spring: "The methods inside this class will handle HTTP requests and return data." Instead of returning web pages, it usually returns: JSON Strings Objects API responses Whenever I create a new API endpoint, this is one of the first annotations I add. 3. @GetMapping This annotation is used when you want to handle GET requests . @GetMapping ( "/students" ) public String getStudents () { return "List of students" ; } A GET request is typically used to retrieve information. Examples: Get user details Fetch products View student records Whenever a client requests data from the server, @GetMapping often comes into play. 4. @PostMapping While @GetMapping retrieves data, @PostMappin
AI 资讯
Your @EventListener Fires Before the Transaction Commits⚙️
Your domain event fires. Your notification service queries the DB for the entity that just got saved. It finds nothing. You add a log line. It starts working. You remove the log. It breaks again. That's not a race condition. That's @EventListener . What's actually happening Spring's @EventListener fires synchronously, inside the calling thread, before the transaction commits. The DB row exists in Hibernate's session — but it hasn't been flushed and committed yet. Other connections, including the one your listener opens when it calls findById , can't see it. The log statement "fixes" it because the delay gives Hibernate time to flush. Remove the log, the flush doesn't happen in time, and you're back to an empty Optional . Here's the broken setup: @Component public class OrderEventListener { @EventListener // fires MID-TRANSACTION, before commit public void onOrderCreated ( OrderCreatedEvent event ) { // Transaction not committed yet. // Other DB connections see nothing. Order order = orderRepository . findById ( event . getOrderId ()) . orElseThrow (); // ← throws here, row doesn't exist yet notificationService . notifyCustomer ( order ); } } The obvious fix and what it costs you Spring ships @TransactionalEventListener for exactly this. Set phase = TransactionPhase.AFTER_COMMIT and the listener fires after the transaction commits. The row is visible. findById returns the order. Problem solved. @Component public class OrderEventListener { @TransactionalEventListener ( phase = TransactionPhase . AFTER_COMMIT ) public void onOrderCreated ( OrderCreatedEvent event ) { // Transaction committed. All connections see the row. Order order = orderRepository . findById ( event . getOrderId ()) . orElseThrow (); // ← works fine notificationService . notifyCustomer ( order ); } } But the trade-off is real. Your listener is now decoupled from the transaction. If the listener fails — notification service is down, the email throws, the external API times out — the transaction alrea
AI 资讯
Building an AI Chat Agent with MCP, Spring AI
Model Context Protocol (MCP) is an open standard for connecting AI apps to tools and data sources. A useful way to think about it is as a USB-C port for AI: one standard interface that lets different models plug into different capabilities without custom glue code for every integration. In this project, we combine MCP, Spring AI, and Google Gemini to build a chat app that can answer weather questions using real tools instead of hallucinating. The system has three parts: MCP tool server - a Spring Boot service that exposes weather and geocoding tools AI chat agent - a Spring Boot service that uses Spring AI + Gemini and calls MCP tools when needed React chat UI - a lightweight frontend for sending messages and rendering replies The result is a small but realistic architecture you can extend into a production assistant. Architecture User (Browser:3000) | POST /api/chat v AI Agent (Spring:7171) -- MCP / Streamable HTTP --> MCP Server (Spring:7170) | | | Google Gemini | Bright Sky API (weather) | | OpenStreetMap Nominatim (geocoding) v v Chat response Tool execution The full source code is available on GitHub . 1. The MCP Tool Server The tool server is a Spring Boot application that exposes MCP tools through Spring AI's annotation scanner. It runs on port 7170 and uses Streamable HTTP for transport. Dependencies <dependency> <groupId> org.springframework.ai </groupId> <artifactId> spring-ai-starter-mcp-server-webmvc </artifactId> </dependency> <dependency> <groupId> org.springframework.boot </groupId> <artifactId> spring-boot-starter-web </artifactId> </dependency> Defining tools With Spring AI, a tool is just a Spring bean method annotated with @McpTool : @Component public class WeatherTool { private final WeatherToolService weatherToolService ; public WeatherTool ( WeatherToolService weatherToolService ) { this . weatherToolService = weatherToolService ; } @McpTool ( name = "get_current_weather" , description = "Get current weather by dwd_station_id or by lat/lon" ) p