Hinweis: Dieser Artikel basiert auf Spring AI 2.0.0 (GA), das Spring Boot 4.0.x/4.1.x voraussetzt.

KI ist längst nicht mehr nur ein Python-Thema. Mit Spring AI bringt das Spring-Team eine offizielle Abstraktionsschicht für Large Language Models (LLMs) und verwandte KI-Dienste in das Java-Ökosystem. Das Ziel: KI-Funktionen mit denselben bekannten Mustern nutzen, die Spring-Entwickler von JPA, REST und Messaging gewohnt sind – inklusive Auto-Configuration, Dependency Injection und Testbarkeit.

Installation

Spring AI wird wie jedes andere Spring Boot Modul über den Starter integriert:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
Code-Sprache: HTML, XML (xml)

Für den Zugriff auf OpenAI wird ein API-Key benötigt:

spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.model=gpt-4o

Spring AI unterstützt zahlreiche Provider über dieselbe einheitliche API: Anthropic, Ollama, Mistral, Azure OpenAI, Google GenAI, DeepSeek, Groq, NVIDIA, Perplexity und weitere.

Der ChatClient

Das zentrale Bean ist der ChatClient. Er wird über einen Builder erstellt und kann System-Prompts erhalten:

@Configuration
class AIConfig {

    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder
            .defaultSystem("Du bist ein hilfsbereiter Java-Experte. " +
                "Antworte prägnant in Deutsch.")
            .build();
    }
}
Code-Sprache: CSS (css)

Einfache Verwendung:

@Service
public class CodeAssistant {

    private final ChatClient chatClient;

    public CodeAssistant(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    public String erklaereFehler(String fehlermeldung) {
        return chatClient.prompt()
            .user("Erkläre diesen Java-Fehler: {fehler}", fehlermeldung)
            .call()
            .content();
    }
}
Code-Sprache: PHP (php)

call() sendet den Prompt synchron. Für asynchrone Streaming-Verarbeitung gibt es stream().chatResponse() mit Rückgabe eines Flux<ChatResponse>:

Flux<String> stream = chatClient.prompt()
    .user("Erkläre mir Java Streams")
    .stream()
    .content();
Code-Sprache: JavaScript (javascript)

Prompt Templates und Structured Outputs

Spring AI bietet Prompt-Templates mit Platzhaltern, ähnlich wie @Query in Spring Data JPA:

String antwort = chatClient.prompt()
    .user(u -> u.text("""
        Generiere eine Java-Klasse mit folgenden Eigenschaften:
        - Klassenname: {className}
        - Felder: {fields}
        - Mit Gettern, Settern und Konstruktor.
        Gib NUR den Java-Code zurück, keine Erklärungen.
        """)
        .param("className", "Person")
        .param("fields", "name:String, alter:int"))
    .call()
    .content();
Code-Sprache: PHP (php)

Für strukturierte JSON-Antworten bietet Spring AI den BeanOutputConverter und die praktische .entity()-Methode:

record Person(String name, int alter, String email) {}

<em>// Einfache Variante mit ChatClient:</em>
Person person = chatClient.prompt()
    .user("Gib eine zufällige Person zurück.")
    .call()
    .entity(Person.class);

System.out.println(person.name()); <em>// Automatisch deserialisiert</em>
Code-Sprache: JavaScript (javascript)

Die .entity()-Methode generiert automatisch ein JSON-Schema, weist das LLM an, gültiges JSON im Zielformat zurückzugeben, und deserialisiert die Antwort. Für komplexere Typen wie Listen verwendet man ParameterizedTypeReference:

List<Person> personen = chatClient.prompt()
    .user("Gib 5 zufällige Personen zurück.")
    .call()
    .entity(new ParameterizedTypeReference<List<Person>>() {});
Code-Sprache: PHP (php)

Für noch zuverlässigere Ergebnisse unterstützt Spring AI auch Native Structured Output bei kompatiblen Modellen (z.B. GPT-4o, Claude 3.5+):

Person person = chatClient.prompt()
    .advisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
    .user("Gib eine zufällige Person zurück.")
    .call()
    .entity(Person.class);
Code-Sprache: PHP (php)

Tool Calling

LLMs können über Tool Calling externe Java-Methoden als „Werkzeuge“ aufrufen – das Modell entscheidet selbst, wann und mit welchen Argumenten:

@Component
public class WetterService {

    @Tool(description = "Gibt das aktuelle Wetter für eine Stadt zurück")
    public String currentWeather(
            @ToolParam(description = "Die Stadt") String stadt) {
        <em>// Ruft echte Wetter-API auf</em>
        return api.getWetter(stadt);
    }
}

<em>// Im ChatClient registrieren:</em>
String antwort = chatClient.prompt()
    .user("Wie ist das Wetter in Hamburg?")
    .tools(new WetterService())
    .call()
    .content();

<em>// Das LLM erkennt, dass es das Wetter-Tool für "Hamburg" aufrufen muss,</em>
<em>// Spring AI führt die Java-Methode aus und gibt das Ergebnis ans LLM zurück</em>
Code-Sprache: JavaScript (javascript)

Das LLM erhält eine Beschreibung der verfügbaren Tools und ihrer Parameter. Bei Bedarf ruft es sie selbstständig auf – ganz ohne If-Then-Logik im Code. Spring AI registriert automatisch einen ToolCallingAdvisor, der den gesamten Tool-Calling-Lebenszyklus verwaltet.

Embeddings und Vektor-Datenbanken

Für semantische Suche und RAG (Retrieval Augmented Generation) bietet Spring AI das EmbeddingModel-Interface:

@Service
public class EmbeddingService {

    private final EmbeddingModel embeddingModel;

    public EmbeddingService(EmbeddingModel embeddingModel) {
        this.embeddingModel = embeddingModel;
    }

    public float[] textZuVektor(String text) {
        return embeddingModel.embed(text);
    }
}
Code-Sprache: PHP (php)

In Kombination mit einer Vektor-Datenbank wie Pgvector (PostgreSQL), Redis, Pinecone oder einer der vielen anderen unterstützten Datenbanken entsteht eine vollständige RAG-Pipeline:

<em>// Dokumente indizieren</em>
vektorStore.add(List.of(
    new Document("Spring Boot ist ein Framework...",
        Map.of("quelle", "docs"))));

<em>// Semantisch suchen</em>
List<Document> treffer = vektorStore.similaritySearch(
    SearchRequest.builder()
        .query("Wie starte ich Spring Boot?")
        .topK(3)
        .build());

<em>// Dem LLM als Kontext mitgeben</em>
String prompt = String.format("""
    Beantworte die Frage basierend auf diesen Dokumenten:
    %s
    
    Frage: %s
    """, treffer, frage);
Code-Sprache: PHP (php)

Noch eleganter geht es mit dem QuestionAnswerAdvisor, der RAG direkt in den ChatClient integriert:

String antwort = chatClient.prompt()
    .advisors(new QuestionAnswerAdvisor(vektorStore))
    .user("Wie starte ich Spring Boot?")
    .call()
    .content();
Code-Sprache: JavaScript (javascript)

So kann das LLM auf unternehmenseigene Dokumente zugreifen, die nie in seinem Trainingsdatensatz waren.

Bild- und Audio-Generierung

Spring AI abstrahiert auch multimodale Modelle:

<em>// Bildgenerierung mit DALL-E</em>
ImageResponse response = imageModel.call(
    new ImagePrompt("Ein roter Vogel auf einem blauen Baum",
        OpenAiImageOptions.builder()
            .quality("hd")
            .n(1)
            .height(1024)
            .width(1024)
            .build()));

String imageUrl = response.getResult().getOutput().getUrl();
Code-Sprache: JavaScript (javascript)

Neben der Bildgenerierung unterstützt Spring AI auch Audio-Transkription (Whisper) und Text-to-Speech über dieselbe einheitliche API.

Advisors und Chat Memory

Spring AI 2.0 führt die Advisors API ein – ein mächtiges Konzept, das wiederkehrende KI-Muster kapselt. Advisors können Prompts transformieren, Kontext hinzufügen oder Antworten modifizieren:

String antwort = chatClient.prompt()
    .advisors(
        new MessageChatMemoryAdvisor(chatMemory),  <em>// Konversationsgedächtnis</em>
        new QuestionAnswerAdvisor(vektorStore),     <em>// RAG</em>
        new SimpleLoggerAdvisor()                   <em>// Logging</em>
    )
    .user(userText)
    .call()
    .content();
Code-Sprache: JavaScript (javascript)

Mit ChatMemory lässt sich zustandsbehaftete Konversation umsetzen – das LLM „erinnert“ sich an frühere Nachrichten:

ChatMemory chatMemory = MessageWindowChatMemory.builder()
    .maxMessages(20)
    .build();

<em>// Bei jedem Aufruf die Conversation-ID mitgeben:</em>
String antwort = chatClient.prompt()
    .advisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
    .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, sessionId))
    .user("Mein Name ist Max")
    .call()
    .content();
Code-Sprache: JavaScript (javascript)

Model Context Protocol (MCP)

Eine der wichtigsten Neuerungen in Spring AI 2.0 ist die Unterstützung des Model Context Protocol (MCP) – ein offener Standard für die Verbindung von KI-Assistenten mit externen Datenquellen und Tools. Über MCP können LLMs dynamisch auf APIs, Datenbanken und andere Ressourcen zugreifen, ohne dass diese fest im Code verdrahtet sein müssen.

Fazit

Spring AI 2.0 katapultiert Java in die KI-Welt, ohne dass Entwickler das Spring-Ökosystem verlassen müssen. Die Abstraktion über ChatClientEmbeddingModel und Tool Calling folgt denselben Mustern wie JPA-Templates oder REST-Clients. Besonders die Kombination aus Advisors, Tool Calling, RAG über Vektor-Datenbanken und MCP macht Java-Anwendungen zu ernstzunehmenden KI-Applikationen – mit Testbarkeit, Konfigurierbarkeit und Produktionstauglichkeit, wie man es von Spring gewohnt ist.