{"id":70117,"date":"2026-09-24T12:28:28","date_gmt":"2026-09-24T10:28:28","guid":{"rendered":"https:\/\/www.inovex.de\/?p=70117"},"modified":"2026-09-24T12:28:28","modified_gmt":"2026-09-24T10:28:28","slug":"spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python","status":"publish","type":"post","link":"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/","title":{"rendered":"Spring AI 2.0: AI in the Java Stack, Without Having to Go Through Python"},"content":{"rendered":"<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_88 counter-hierarchy ez-toc-counter ez-toc-custom ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\"><p class=\"ez-toc-title\" style=\"cursor:inherit\">Inhaltsverzeichnis<\/p>\n<\/div><nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#What-is-Spring-AI%E2%80%94and-what-problem-does-it-solve\" >What is Spring AI\u2014and what problem does it solve?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#Spring-AI-or-Python-After-All\" >Spring AI or Python After All?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#Key-Concepts\" >Key Concepts<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#The-Minimal-Setup\" >The Minimal Setup<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#1-Dependencies\" >1. Dependencies<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#2-Configuration\" >2. Configuration<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#3-A-First-Controller\" >3. A First Controller<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#Responses-Directly-as-Objects\" >Responses Directly as Objects<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#Streaming\" >Streaming<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#Strengths-and-Weaknesses%E2%80%94An-Honest-Assessment\" >Strengths and Weaknesses\u2014An Honest Assessment<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#In-Practice-Proven-Patterns\" >In Practice: Proven Patterns<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#RAG-Instead-of-Fine-Tuning\" >RAG Instead of Fine-Tuning<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#Take-System-Prompts-Seriously\" >Take System Prompts Seriously<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#Move-Prompts-to-Separate-Files\" >Move Prompts to Separate Files<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#Tools-Instead-of-Blind-Answers\" >Tools Instead of Blind Answers<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#Managing-the-Conversation-History\" >Managing the Conversation History<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#Securing-Endpoints\" >Securing Endpoints<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#What-Else-20-Brings-to-the-Table\" >What Else 2.0 Brings to the Table<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-19\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#Conclusion\" >Conclusion<\/a><\/li><\/ul><\/nav><\/div>\n<p><em>This article was automatically translated. <a href=\"https:\/\/www.inovex.de\/de\/blog\/spring-ai-2-0-ki-im-java-stack-ohne-den-umweg-ueber-python\/\">Original article<\/a>.<\/em><\/p>\n<p>AI has made its way into virtually every product team, and with it comes a question that has long preoccupied Java developers: Do you actually have to switch to Python for serious AI features? For some time now, the short answer has been: No. Spring AI provides the longer answer.<\/p>\n<p>With the GA release of Spring AI 2.0 in June 2026, the project has reached a stable foundation\u2014built on Spring Boot 4 and Spring Framework 7. This article explains what Spring AI is all about, what a minimal setup looks like, and what to keep in mind in day-to-day use. It\u2019s intended for developers who want to get a solid first impression before integrating the framework into an existing project.<!--more--><\/p>\n<table>\n<tbody>\n<tr>\n<td><b>Spring AI<\/b><\/td>\n<td>2.0.0 (GA, June 2026), available via Maven Central<\/td>\n<\/tr>\n<tr>\n<td><b>Platform<\/b><\/td>\n<td>Spring Boot 4.0<\/td>\n<\/tr>\n<tr>\n<td><b>Java<\/b><\/td>\n<td>17 as a minimum; 21+ recommended<\/td>\n<\/tr>\n<tr>\n<td>Libraries<\/td>\n<td>Jackson 3 instead of Jackson 2; consistently annotated with \u201cnull\u201d (JSpecify)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2><span class=\"ez-toc-section\" id=\"What-is-Spring-AI%E2%80%94and-what-problem-does-it-solve\"><\/span>What is Spring AI\u2014and what problem does it solve?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Spring AI is a framework from the Spring community that integrates AI capabilities into Spring applications without requiring developers to leave their familiar technology stack. It provides abstractions for working with models from various providers\u2014such as those from OpenAI, Anthropic (Claude), or Google (Gemini).<\/p>\n<p>Conceptually, the project borrows heavily from Python libraries like LangChain and LlamaIndex. The difference: Spring AI thinks in Spring terms from the very beginning. Dependency injection, auto-configuration, the usual design patterns\u2014all of that remains intact. For a seasoned Spring team, this significantly lowers the barrier to entry because no second mental model is required.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Spring-AI-or-Python-After-All\"><\/span>Spring AI or Python After All?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>No one disputes that Python is the top choice in the AI and ML landscape. So why Spring AI? The reason rarely lies in pure AI performance, but rather in integration.<\/p>\n<p>Imagine an application that has grown over the years\u2014Spring Petclinic is the classic example, built on Spring Boot, Thymeleaf, and JPA. Such systems were never designed for AI. The alternative to Spring AI would be to set up a second infrastructure in Python alongside it: a separate service, separate authentication, additional network hops, and another CI\/CD pipeline. All of that comes at a cost before the first feature is even up and running.<\/p>\n<p>Spring AI takes the opposite approach. Just as Spring Data provides an abstraction layer over various databases\u2014you s<\/p>\n<p>&nbsp;<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Key-Concepts\"><\/span>Key Concepts<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>There are a few terms you should know before getting started.<\/p>\n<p><strong>Models<\/strong> \u2013 the actual AI algorithms. Spring AI supports the major providers: OpenAI, Anthropic Claude, Google Gemini, Amazon Bedrock, Ollama for local models, and others. In version 2.0, the framework for OpenAI, Anthropic, and Google uses the official vendor SDKs directly, making new model features available more quickly.<\/p>\n<p><strong>Tokens &amp; Embeddings<\/strong> \u2013 Models don\u2019t process words, but rather tokens, which are text fragments. Embeddings translate text into numerical vectors, allowing semantic proximity to be expressed mathematically. This is the foundation for vector search and RAG.<\/p>\n<p><strong>Prompts<\/strong> \u2013 the instructions given to the model. For dynamic prompts, Spring AI relies on templates (via StringTemplate), into which variables are inserted at runtime.<\/p>\n<p><strong>ChatClient<\/strong> \u2013 the central, fluent API for communicating with the model. In version 2.0, the ChatClient is explicitly the recommended entry point; the lower-level ChatModel is only needed for special cases.<\/p>\n<p><strong>Advisors<\/strong> \u2013 a kind of AOP for LLM calls. They intercept requests and responses: The SimpleLoggerAdvisor logs requests, while the MessageChatMemoryAdvisor automatically appends the conversation history.<\/p>\n<p><strong>Structured Output<\/strong> \u2013 By default, LLMs respond in free-form text. Spring AI can configure the model to return machine-readable JSON instead, which is mapped directly to a Java record or POJO.<\/p>\n<p><strong>Tool Calling<\/strong> \u2013 The model may call defined<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The-Minimal-Setup\"><\/span>The Minimal Setup<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Thanks to auto-configuration, getting started is quick.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"1-Dependencies\"><\/span>1. Dependencies<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Since version 2.0, the stable artifacts have been available in Maven Central\u2014you no longer need an additional snapshot or milestone repository. The best way to manage versions is via the BOM (here, Gradle Kotlin DSL):<\/p>\n<pre class=\"\">dependencies {\r\n \u00a0\u00a0\u00a0implementation(platform(\"org.springframework.ai:spring-ai-bom:2.0.0\"))\r\n \u00a0\u00a0\u00a0implementation(\"org.springframework.ai:spring-ai-starter-model-openai\")\r\n}<\/pre>\n<p>For Anthropic or Google, you simply swap out the starter (spring-ai-starter-model-anthropic, spring-ai-starter-model-google-genai)\u2014the rest of the code remains the same. That\u2019s exactly the point.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"2-Configuration\"><\/span>2. Configuration<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>The API key is retrieved from an environment variable.<\/p>\n<pre class=\"\">spring:\r\n \u00a0ai:\r\n \u00a0\u00a0\u00a0openai:\r\n \u00a0\u00a0\u00a0\u00a0\u00a0api-key: ${OPENAI_API_KEY}\r\n \u00a0\u00a0\u00a0\u00a0\u00a0chat:\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0options:\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0model: gpt-5-mini\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0temperature: 0.0 \u00a0 # 0.0 = deterministisch, h\u00f6here Werte = kreativer<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"3-A-First-Controller\"><\/span>3. A First Controller<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>You can use ChatClient.Builder to inject a ready-to-use client:<\/p>\n<pre class=\"\">@RestController\r\n@RequestMapping(\"\/api\/ai\")\r\npublic class TranslationController {\r\n \u00a0\u00a0\u00a0private final ChatClient chatClient;\r\n \u00a0\u00a0\u00a0public TranslationController(ChatClient.Builder builder) {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0this.chatClient = builder.build();\r\n \u00a0\u00a0\u00a0}\r\n\r\n \u00a0\u00a0\u00a0@GetMapping(\"\/translate\")\r\n \u00a0\u00a0\u00a0public Map&lt;String, String&gt; translate(@RequestParam String text) {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0String response = chatClient.prompt()\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.user(\"\u00dcbersetze den folgenden Text ins Spanische: \" + text)\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.call()\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.content();\r\n\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return Map.of(\"translation\", response);\r\n \u00a0\u00a0\u00a0}\r\n}<\/pre>\n<p>The call generates the request to the provider in the background and returns plain text. That&#8217;s all it takes for the first result.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Responses-Directly-as-Objects\"><\/span>Responses Directly as Objects<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Often, you want structure rather than plain text. The ChatClient can map the response directly to a record using .entity(&#8230;):<\/p>\n<pre class=\"\">record Translation(String original, String translated, String targetLanguage) {}\r\n\r\nTranslation result = chatClient.prompt()\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.user(\"\u00dcbersetze 'Guten Morgen' ins Spanische.\")\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.call()\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.entity(Translation.class);<\/pre>\n<p>Spring AI instructs the model to return the appropriate JSON and deserializes it for us.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Streaming\"><\/span>Streaming<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>With longer responses, you don&#8217;t want to wait until the entire text is available. .stream() returns the response as a Flux, piece by piece:<\/p>\n<pre class=\"\">@GetMapping(value = \"\/chat\/stream\", produces = MediaType.TEXT_EVENT_STREAM_VALUE)\r\npublic Flux&lt;String&gt; stream(@RequestParam String message) {\r\n \u00a0\u00a0\u00a0return chatClient.prompt()\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.user(message)\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.stream()\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.content();\r\n}<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"Strengths-and-Weaknesses%E2%80%94An-Honest-Assessment\"><\/span>Strengths and Weaknesses\u2014An Honest Assessment<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>No tech stack is all sunshine and roses. Spring AI clearly excels in three areas.<\/p>\n<p>The first is model interchangeability. Switching from OpenAI to a model run locally via Ollama is often just a matter of updating dependencies and a few properties; the application code remains unaffected. This makes A\/B testing between providers and mixing commercial and local models effortless.<\/p>\n<p>The second is integration with Spring Boot. Vector databases like Qdrant, PgVector, or Redis connect to the application via the usual auto-configuration\u2014saving a lot of setup work.<\/p>\n<p>The third is security. The Model Context Protocol (MCP) allows AI endpoints to be secured, either via OAuth2 (as specified in the standard) or, where no OAuth2 infrastructure exists, via an API key.<\/p>\n<p>However, there are limitations. The project has long moved at a rapid pace. With 2.0 GA, the API surface has stabilized and is consistently zero-annotated, but anyone migrating from the 1.x world should definitely read the upgrade notes\u2014quite a few things have been renamed or removed between versions. Fine-tuning remains challenging: A good overall configuration consisting of the model, vector store, and prompt strategy isn\u2019t achieved by default, but rather through measurement and readjustment. And local open-source models are often still inferior to commercial models in terms of dialogue handling; the framework cannot abstract this away.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"In-Practice-Proven-Patterns\"><\/span>In Practice: Proven Patterns<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Anyone who dives deeper will encounter the same challenges as everyone else. Here are a few recommendations.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"RAG-Instead-of-Fine-Tuning\"><\/span>RAG Instead of Fine-Tuning<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>If the model needs to access internal company knowledge, Retrieval Augmented Generation (RAG) is usually the more cost-effective approach than fine-tuning. Unstructured content\u2014such as tickets and wiki articles\u2014is stored as embeddings in a vector store. At runtime, the pipeline searches for semantically relevant fragments and provides them to the model as context.<\/p>\n<p>The vector store is configured as a bean. Since version 1.0, Spring AI has consistently used builders instead of multi-argument constructors:<\/p>\n<pre class=\"\">@Bean\r\nQdrantVectorStore vectorStore(QdrantClient client, EmbeddingModel embeddingModel) {\r\n \u00a0\u00a0\u00a0return QdrantVectorStore.builder(client, embeddingModel)\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.collectionName(\"incidents\")\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.initializeSchema(true)\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.build();\r\n}<\/pre>\n<p>You don&#8217;t have to build the search manually. QuestionAnswerAdvisor handles retrieval and prompt enrichment:<\/p>\n<pre class=\"\">ChatClient chatClient = builder\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore).build())\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.build();<\/pre>\n<p>If you need more control, you can access the Store directly\u2014using the Builder here as well:<\/p>\n<pre class=\"\">SearchRequest request = SearchRequest.builder()\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.query(text)\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.topK(5)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.build();\r\n\r\nList&lt;Document&gt; hits = vectorStore.similaritySearch(request);<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Take-System-Prompts-Seriously\"><\/span>Take System Prompts Seriously<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Models are also willing to answer questions that have nothing to do with the actual task. A system prompt sets clear guidelines:<\/p>\n<pre class=\"\">@Bean\r\nChatClient chatClient(ChatClient.Builder builder) {\r\n \u00a0\u00a0\u00a0return builder\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.defaultSystem(\"\"\"\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Du bist ein Assistent f\u00fcr die Verwaltung einer Tierklinik.\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Beantworte ausschlie\u00dflich Fragen, die die Tierklinik betreffen.\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"\"\")\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.build();\r\n}<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"Move-Prompts-to-Separate-Files\"><\/span>Move Prompts to Separate Files<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>These kinds of instructions can quickly become lengthy. Strings concatenated within Java code are difficult to maintain\u2014and product owners or prompt engineers can\u2019t even access them. A better approach is to place the prompt in a file. Markdown works well because it allows for a structure that the models can reliably process. Spring AI loads .st templates by default; a simple Markdown file works just as well.<\/p>\n<p>File located at src\/main\/resources\/prompts\/chat-system.md:<\/p>\n<pre class=\"\"># Rolle\r\nDu bist ein Assistent f\u00fcr eine Tierklinik.\r\n\r\n# Regeln\r\n- Beantworte nur Fragen zur Tierklinik.\r\n- Gib niemals sensible Kundendaten preis.\r\n- Wenn du etwas nicht wei\u00dft, sage das offen.<\/pre>\n<p>Load via @Value as Classpath-Ressource:<\/p>\n<pre class=\"\">@Configuration\r\npublic class ChatClientConfig {\r\n \u00a0\u00a0\u00a0@Value(\"classpath:prompts\/chat-system.md\")\r\n \u00a0\u00a0\u00a0private Resource systemPrompt;\r\n\r\n \u00a0\u00a0\u00a0@Bean\r\n \u00a0\u00a0\u00a0ChatClient chatClient(ChatClient.Builder builder) {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return builder.defaultSystem(systemPrompt).build();\r\n \u00a0\u00a0\u00a0}\r\n}<\/pre>\n<p>Advantage: The Java class remains clean, and the prompt can be customized independently of the code.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Tools-Instead-of-Blind-Answers\"><\/span>Tools Instead of Blind Answers<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>With Tool Calling, you give the model access to your own systems\u2014it decides for itself if and when to call a method. The streamlined approach uses the @Tool annotation on standard service methods. The description is key: it\u2019s what tells the model when the tool is appropriate.<\/p>\n<p>A common pitfall: The annotation is located in org.springframework.ai.tool.annotation.Tool\u2014not in the chat.model package.<\/p>\n<pre class=\"\">import org.springframework.ai.tool.annotation.Tool;\r\n\r\nimport org.springframework.ai.tool.annotation.ToolParam;\r\n\r\nimport org.springframework.stereotype.Service;\r\n\r\n\u00a0\r\n\r\n@Service\r\n\r\npublic class WeatherService {\r\n \u00a0\u00a0\u00a0@Tool(description = \"Liefert aktuelle Temperatur und Wetter f\u00fcr eine Stadt.\")\r\n \u00a0\u00a0\u00a0public String getCurrentWeather(\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0@ToolParam(description = \"Name der Stadt\") String city) {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return new ExternalWeatherApiClient().fetchWeatherForCity(city);\r\n \u00a0\u00a0\u00a0}\r\n}<\/pre>\n<p>To register the tool, pass the Bean\u2014not the method name as a string. Per request using .tools(&#8230;), or for all requests in the builder using .defaultTools(&#8230;):<\/p>\n<pre class=\"\">@RestController\r\n\r\npublic class WeatherChatController {\r\n\u00a0\u00a0\u00a0\u00a0private final ChatClient chatClient;\r\n\r\n \u00a0\u00a0\u00a0public WeatherChatController(ChatClient.Builder builder, WeatherService weatherService) {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0this.chatClient = builder\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.defaultTools(weatherService)\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.build();\r\n\u00a0\u00a0\u00a0\u00a0}\r\n\r\n \u00a0\u00a0\u00a0@GetMapping(\"\/chat\")\r\n \u00a0\u00a0\u00a0public String chat(@RequestParam String message) {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return chatClient.prompt()\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.user(message)\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.call()\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.content();\r\n \u00a0\u00a0\u00a0}\r\n}<\/pre>\n<p>Spring AI automatically generates the JSON schema expected by the model from the method signature, calls the method as needed, and returns the result. In version 2.0, the ChatModels no longer execute this loop themselves\u2014instead, the ChatClient automatically registers a ToolCallingAdvisor for this purpose. Anyone working directly with the ChatModel must control the tool execution themselves.<\/p>\n<p>The older approach using `java.util.function.Function` beans with `@Description` still exists; however, for new projects, `@Tool` is the more direct approach.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Managing-the-Conversation-History\"><\/span>Managing the Conversation History<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>LLMs are stateless\u2014without a history, the model cannot understand follow-up questions. The `MessageChatMemoryAdvisor` automatically appends the latest messages. Spring Boot already configures a `ChatMemory` bean (a `MessageWindowChatMemory` with an in-memory repository) for this purpose; you just need to integrate it:<\/p>\n<pre class=\"\">@Bean\r\nChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) {\r\n \u00a0\u00a0\u00a0return builder\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.defaultAdvisors(\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0MessageChatMemoryAdvisor.builder(chatMemory).build(),\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0new SimpleLoggerAdvisor())\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.build();\r\n}<\/pre>\n<p>If you want to set the window size yourself, you must explicitly create the Memory:<\/p>\n<pre class=\"\">ChatMemory chatMemory = MessageWindowChatMemory.builder()\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.maxMessages(20)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.build();<\/pre>\n<p>One detail that is required in current versions: Every call made through the Memory Advisor requires a conversation ID to ensure that conversations remain separate:<\/p>\n<pre class=\"\">chatClient.prompt()\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.user(message)\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.advisors(a -&gt; a.param(ChatMemory.CONVERSATION_ID, conversationId))\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.call()\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.content();<\/pre>\n<p>The previously common syntax using `new InMemoryChatMemory()` is no longer used\u2014the constructor and class have been replaced by the Builder API.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Securing-Endpoints\"><\/span>Securing Endpoints<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In enterprise environments, securing AI integration is not just a \u201cnice-to-have.\u201d When integrating systems via MCP, MCP servers must be protected according to the specification\u2014primarily via OAuth2. Where this is not feasible, the community module `mcp-security` offers an API key option. It does not originate from Spring Security Core, but from `org.springaicommunity:mcp-server-security` (for Spring AI 2.x, the 0.1.x version line).<\/p>\n<pre class=\"\">@Bean\r\nSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\r\n \u00a0\u00a0\u00a0\/\/ McpApiKeyConfigurer stammt aus dem Community-Modul mcp-server-security\r\n \u00a0\u00a0\u00a0return http\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.authorizeHttpRequests(auth -&gt; auth.anyRequest().authenticated())\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.with(McpApiKeyConfigurer.mcpServerApiKey(), apiKey -&gt;\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0apiKey.apiKeyRepository(apiKeyRepository()))\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0.build();\r\n}<\/pre>\n<p>The apiKeyRepository is required\u2014the configuration will not start without a key source. The server is then called with an X-API-key: id.secret header; the secret portion is bcrypt-hashed on the server side.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"What-Else-20-Brings-to-the-Table\"><\/span>What Else 2.0 Brings to the Table<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>A few points that are interesting beyond day-to-day operations.<\/p>\n<p>MCP has been fully integrated into the core in 2.0. An application can simultaneously act as an MCP client\u2014consuming external tools such as file system or database access\u2014and as an MCP server, offering its own business logic as tools. Streamable HTTP is the new standard for transport; SSE is considered deprecated, while stdio remains for local processes.<\/p>\n<p>Observability is built-in. Spring AI generates micrometer spans and OpenTelemetry-compatible metrics for model and tool calls, including token consumption\u2014which helps with cost control.<\/p>\n<p>The entire API is null-annotated via JSpecify. For Kotlin users, this translates to true nullable and non-nullable types that the compiler checks.<\/p>\n<p>In production, it\u2019s also worth planning for retry and rate-limiting behavior, as well as sensible error handling, from the very beginning: LLM APIs don\u2019t always respond, and they don\u2019t always respond quickly. And when setting up a new project, start.spring.io takes care of selecting the model and vector store starters for you.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Conclusion\"><\/span>Conclusion<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Spring AI bridges the gap between the established Java world and the rapid development in generative AI without requiring you to leave the ecosystem. Through abstractions like the ChatClient, straightforward tool calling, and the integration of vector stores, you can create value instead of rebuilding infrastructure.<\/p>\n<p>With version 2.0, the entire framework rests on a stable, consistent foundation. For those familiar with the Spring Boot ecosystem who want to integrate LLMs, this provides a straightforward path. The pragmatic approach: start small with the ChatClient, experiment with structured output, and later expand the architecture toward RAG.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This article was automatically translated. Original article. AI has made its way into virtually every product team, and with it comes a question that has long preoccupied Java developers: Do you actually have to switch to Python for serious AI features? For some time now, the short answer has been: No. Spring AI provides the [&hellip;]<\/p>\n","protected":false},"author":463,"featured_media":70114,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"ep_exclude_from_search":false,"footnotes":""},"tags":[13,1291],"service":[473,465],"level":[1280],"coauthors":[{"id":463,"display_name":"Andrej Litowka","user_nicename":"alitowka"}],"class_list":["post-70117","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","tag-ai","tag-java","service-artificial-intelligence-en","service-backend-en"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Spring AI 2.0: AI in the Java Stack, Without Having to Go Through Python - inovex GmbH<\/title>\n<meta name=\"description\" content=\"Do you really need Python for AI? Spring AI 2.0 brings LLM features directly into your Java stack. Learn everything about setup, ChatClient, RAG, and best practices.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Spring AI 2.0: AI in the Java Stack, Without Having to Go Through Python - inovex GmbH\" \/>\n<meta property=\"og:description\" content=\"Do you really need Python for AI? Spring AI 2.0 brings LLM features directly into your Java stack. Learn everything about setup, ChatClient, RAG, and best practices.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/\" \/>\n<meta property=\"og:site_name\" content=\"inovex GmbH\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/inovexde\" \/>\n<meta property=\"article:published_time\" content=\"2026-09-24T10:28:28+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/KI_Java_Stack.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1500\" \/>\n\t<meta property=\"og:image:height\" content=\"880\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Andrej Litowka\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/KI_Java_Stack-1024x601.png\" \/>\n<meta name=\"twitter:creator\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:site\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Andrej Litowka\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/blog\\\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/blog\\\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\\\/\"},\"author\":{\"name\":\"Andrej Litowka\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/#\\\/schema\\\/person\\\/c024a6170e416bf9ddd152490d7e76d1\"},\"headline\":\"Spring AI 2.0: AI in the Java Stack, Without Having to Go Through Python\",\"datePublished\":\"2026-09-24T10:28:28+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/blog\\\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\\\/\"},\"wordCount\":1942,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/blog\\\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/KI_Java_Stack.png\",\"keywords\":[\"Ai\",\"Java\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.inovex.de\\\/en\\\/blog\\\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/blog\\\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\\\/\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/blog\\\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\\\/\",\"name\":\"Spring AI 2.0: AI in the Java Stack, Without Having to Go Through Python - inovex GmbH\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/blog\\\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/blog\\\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/KI_Java_Stack.png\",\"datePublished\":\"2026-09-24T10:28:28+00:00\",\"description\":\"Do you really need Python for AI? Spring AI 2.0 brings LLM features directly into your Java stack. Learn everything about setup, ChatClient, RAG, and best practices.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/blog\\\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.inovex.de\\\/en\\\/blog\\\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/blog\\\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/KI_Java_Stack.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/KI_Java_Stack.png\",\"width\":1500,\"height\":880},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/blog\\\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Spring AI 2.0: AI in the Java Stack, Without Having to Go Through Python\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/#website\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/\",\"name\":\"inovex GmbH\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/#organization\",\"name\":\"inovex GmbH\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2021\\\/03\\\/inovex-logo-16-9-1.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2021\\\/03\\\/inovex-logo-16-9-1.png\",\"width\":1921,\"height\":1081,\"caption\":\"inovex GmbH\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/inovexde\",\"https:\\\/\\\/x.com\\\/inovexgmbh\",\"https:\\\/\\\/www.instagram.com\\\/inovexlife\\\/\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/inovex\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UC7r66GT14hROB_RQsQBAQUQ\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/#\\\/schema\\\/person\\\/c024a6170e416bf9ddd152490d7e76d1\",\"name\":\"Andrej Litowka\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/2b1df2baa1bfb9637037d0e2be466c725e5853c8c4ec9dd80b6ca4498ee3d447?s=96&d=retro&r=gcffcdda02b9c65d5c11cf63dfc7caea7\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/2b1df2baa1bfb9637037d0e2be466c725e5853c8c4ec9dd80b6ca4498ee3d447?s=96&d=retro&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/2b1df2baa1bfb9637037d0e2be466c725e5853c8c4ec9dd80b6ca4498ee3d447?s=96&d=retro&r=g\",\"caption\":\"Andrej Litowka\"},\"url\":\"https:\\\/\\\/www.inovex.de\\\/en\\\/blog\\\/author\\\/alitowka\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Spring AI 2.0: AI in the Java Stack, Without Having to Go Through Python - inovex GmbH","description":"Do you really need Python for AI? Spring AI 2.0 brings LLM features directly into your Java stack. Learn everything about setup, ChatClient, RAG, and best practices.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/","og_locale":"en_US","og_type":"article","og_title":"Spring AI 2.0: AI in the Java Stack, Without Having to Go Through Python - inovex GmbH","og_description":"Do you really need Python for AI? Spring AI 2.0 brings LLM features directly into your Java stack. Learn everything about setup, ChatClient, RAG, and best practices.","og_url":"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/","og_site_name":"inovex GmbH","article_publisher":"https:\/\/www.facebook.com\/inovexde","article_published_time":"2026-09-24T10:28:28+00:00","og_image":[{"width":1500,"height":880,"url":"https:\/\/www.inovex.de\/wp-content\/uploads\/KI_Java_Stack.png","type":"image\/png"}],"author":"Andrej Litowka","twitter_card":"summary_large_image","twitter_image":"https:\/\/www.inovex.de\/wp-content\/uploads\/KI_Java_Stack-1024x601.png","twitter_creator":"@inovexgmbh","twitter_site":"@inovexgmbh","twitter_misc":{"Written by":"Andrej Litowka","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#article","isPartOf":{"@id":"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/"},"author":{"name":"Andrej Litowka","@id":"https:\/\/www.inovex.de\/en\/#\/schema\/person\/c024a6170e416bf9ddd152490d7e76d1"},"headline":"Spring AI 2.0: AI in the Java Stack, Without Having to Go Through Python","datePublished":"2026-09-24T10:28:28+00:00","mainEntityOfPage":{"@id":"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/"},"wordCount":1942,"commentCount":0,"publisher":{"@id":"https:\/\/www.inovex.de\/en\/#organization"},"image":{"@id":"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/KI_Java_Stack.png","keywords":["Ai","Java"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/","url":"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/","name":"Spring AI 2.0: AI in the Java Stack, Without Having to Go Through Python - inovex GmbH","isPartOf":{"@id":"https:\/\/www.inovex.de\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#primaryimage"},"image":{"@id":"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/KI_Java_Stack.png","datePublished":"2026-09-24T10:28:28+00:00","description":"Do you really need Python for AI? Spring AI 2.0 brings LLM features directly into your Java stack. Learn everything about setup, ChatClient, RAG, and best practices.","breadcrumb":{"@id":"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#primaryimage","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/KI_Java_Stack.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/KI_Java_Stack.png","width":1500,"height":880},{"@type":"BreadcrumbList","@id":"https:\/\/www.inovex.de\/en\/blog\/spring-ai-2-0-ai-in-the-java-stack-without-having-to-go-through-python\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.inovex.de\/en\/"},{"@type":"ListItem","position":2,"name":"Spring AI 2.0: AI in the Java Stack, Without Having to Go Through Python"}]},{"@type":"WebSite","@id":"https:\/\/www.inovex.de\/en\/#website","url":"https:\/\/www.inovex.de\/en\/","name":"inovex GmbH","description":"","publisher":{"@id":"https:\/\/www.inovex.de\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.inovex.de\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.inovex.de\/en\/#organization","name":"inovex GmbH","url":"https:\/\/www.inovex.de\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.inovex.de\/en\/#\/schema\/logo\/image\/","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","width":1921,"height":1081,"caption":"inovex GmbH"},"image":{"@id":"https:\/\/www.inovex.de\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/inovexde","https:\/\/x.com\/inovexgmbh","https:\/\/www.instagram.com\/inovexlife\/","https:\/\/www.linkedin.com\/company\/inovex","https:\/\/www.youtube.com\/channel\/UC7r66GT14hROB_RQsQBAQUQ"]},{"@type":"Person","@id":"https:\/\/www.inovex.de\/en\/#\/schema\/person\/c024a6170e416bf9ddd152490d7e76d1","name":"Andrej Litowka","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/2b1df2baa1bfb9637037d0e2be466c725e5853c8c4ec9dd80b6ca4498ee3d447?s=96&d=retro&r=gcffcdda02b9c65d5c11cf63dfc7caea7","url":"https:\/\/secure.gravatar.com\/avatar\/2b1df2baa1bfb9637037d0e2be466c725e5853c8c4ec9dd80b6ca4498ee3d447?s=96&d=retro&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/2b1df2baa1bfb9637037d0e2be466c725e5853c8c4ec9dd80b6ca4498ee3d447?s=96&d=retro&r=g","caption":"Andrej Litowka"},"url":"https:\/\/www.inovex.de\/en\/blog\/author\/alitowka\/"}]}},"_links":{"self":[{"href":"https:\/\/www.inovex.de\/en\/wp-json\/wp\/v2\/posts\/70117","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.inovex.de\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.inovex.de\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/en\/wp-json\/wp\/v2\/users\/463"}],"replies":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/en\/wp-json\/wp\/v2\/comments?post=70117"}],"version-history":[{"count":3,"href":"https:\/\/www.inovex.de\/en\/wp-json\/wp\/v2\/posts\/70117\/revisions"}],"predecessor-version":[{"id":70121,"href":"https:\/\/www.inovex.de\/en\/wp-json\/wp\/v2\/posts\/70117\/revisions\/70121"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/en\/wp-json\/wp\/v2\/media\/70114"}],"wp:attachment":[{"href":"https:\/\/www.inovex.de\/en\/wp-json\/wp\/v2\/media?parent=70117"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.inovex.de\/en\/wp-json\/wp\/v2\/tags?post=70117"},{"taxonomy":"service","embeddable":true,"href":"https:\/\/www.inovex.de\/en\/wp-json\/wp\/v2\/service?post=70117"},{"taxonomy":"level","embeddable":true,"href":"https:\/\/www.inovex.de\/en\/wp-json\/wp\/v2\/level?post=70117"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.inovex.de\/en\/wp-json\/wp\/v2\/coauthors?post=70117"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}