Von Dr. Cassian Holt, Senior Architect bei Java Fleet Systems Consulting

ollama

📚 Was bisher geschah – Teil 1 Recap

In Teil 1 haben wir die Grundlagen gelegt:

  • Use Cases: Code-Review, FAQ-Bots, Dokumentations-Suche, Log-Analyse
  • DSGVO-Compliance: Warum lokale LLMs rechtlich die bessere Wahl sind
  • Quantisierung erklärt: Von FP32 zu INT4 (87% kleiner!)
  • Ollama als Modell-Manager: Der „Docker“ für LLMs
  • Modell-Familien: Llama, Mistral, Phi, Code Llama, DeepSeek
  • Kosten-Analyse: ROI nach 9 Tagen bei 1000 Anfragen/Tag🚀

Die Theorie sitzt. Heute wird gebaut!


Kurze Zusammenfassung – Das Wichtigste in 30 Sekunden

In Teil 1 hast du gelernt, WARUM lokale LLMs die Zukunft sind. Jetzt wird’s praktisch: Wir bauen ein produktionsreifes Spring Boot Multi-Model-System mit Ollama. Du lernst das Maven-Setup, die intelligente Multi-Model-Orchestrierung, Caching-Strategien und Production-Deployment. Das vollständige Projekt steht auf GitHub zum Download bereit. Von Zero zu Production in 30 Minuten!


Moin zurück, Entwickler-Community! 🔬

Cassian hier – und heute wird’s hands-on. Während Liam gerade versucht, meinen Laptop als „KI-Experimentier-Station“ zu übernehmen („Papa, kann ich dem Computer beibringen, Lego zu bauen?“), und Emma philosophisch fragt, ob der Computer auch müde wird, zeige ich dir, wie du lokale LLMs in deine Spring Boot Anwendung integrierst.

Kein theoretisches Gerede mehr – heute schreiben wir Code!

Sarah (meine Frau) meinte heute Morgen: „Du weißt schon, dass normale Menschen einfach ChatGPT nutzen, oder?“ – „Ja,“ sagte ich, „aber normale Menschen verstehen auch nicht, warum ihre Daten in US-Rechenzentren liegen.“ Sie seufzte. Die Zwillinge klatschten. Ich interpretiere das als Zustimmung. 😊

Also, schnapp dir deinen Kaffee (oder bei mir: Tee, weil Sarah sagt, Kaffee um 22 Uhr ist keine gute Idee), und lass uns ein Production-ready LLM-System bauen!


🚀 Warum Spring Boot + Ollama? Die perfekte Kombination

In Teil 1 hast du die Theorie gelernt. Jetzt kommt die Praxis – und ehrlich gesagt: Hier trennt sich die Spreu vom Weizen.

Die Herausforderung: Du kannst die beste Hardware haben, die cleversten Modelle pullen, Ollama perfekt konfigurieren – aber wenn deine Anwendung nicht produktionsreif ist, bleibt es ein Spielzeug. Und Java Fleet baut keine Spielzeuge.

Was „produktionsreif“ wirklich bedeutet:

  1. Nicht nur „es funktioniert“ – sondern es funktioniert auch unter Last
  2. Nicht nur „schnell“ – sondern konsistent schnell (P95 < 2s)
  3. Nicht nur „sicher“ – sondern defense-in-depth (Input-Validation, Rate Limiting, Monitoring)
  4. Nicht nur „billig“ – sondern nachhaltig skalierbar

Warum Spring Boot die richtige Wahl ist:

  • Enterprise-proven: Millionen von Production-Apps weltweit
  • Batteries included: Actuator (Monitoring), Security, Caching out-of-the-box
  • Langchain4j-Integration: Native Support für LLM-Orchestrierung
  • Cloud-ready: Läuft überall (Bare Metal, Docker, Kubernetes, Cloud)

Warum Ollama die richtige Wahl ist:

  • Einfachstes Setup: ollama pull model – fertig
  • REST API: Standard HTTP, kein proprietäres Protokoll
  • Model-Management: Versions, Updates, Rollbacks
  • Resource-Optimierung: Automatisches GPU-Sharing

Die Kombination:

Spring Boot (Application Layer)
       ↕ HTTP REST API
Ollama (Model Layer)
       ↕ llama.cpp
Hardware (GPU/CPU)

Simple, robust, wartbar. Genau wie gute Architektur sein sollte.

Zeit, das in die Realität umzusetzen! 🔧


🎯 Was wir heute bauen: Das Multi-Model-LLM-System

Unser Ziel ist ein intelligentes System, das:

  • 3 verschiedene Modelle parallel verwaltet (Fast/Balanced/Specialized)
  • Automatisch das beste Modell für die Aufgabe wählt
  • REST API bereitstellt (Spring Boot)
  • Caching nutzt (40-60% Request-Reduktion)
  • Rate Limiting hat (Schutz vor Überlastung)
  • Monitoring eingebaut (Metriken, Logs)
  • Production-ready ist (Docker, Health Checks)

Das Endergebnis:

curl http://localhost:8080/api/ai/faq -d "Was ist Spring Boot?"
→ Nutzt Llama 3.2 3B (schnell, 0.5s)

curl http://localhost:8080/api/ai/code-review -d "public void test() {...}"
→ Nutzt DeepSeek Coder 6.7B (spezialisiert, 1.5s)

Warum 3 Modelle statt eins?

Die Mathematik ist gnadenlos:

Szenario: 1000 Anfragen/Tag
- 600 FAQ (einfach)
- 300 Analysen (komplex)
- 100 Code-Reviews (spezialisiert)

Nur großes Modell (8B):
1000 × 2s = 2000s = 33 Min CPU-Zeit

Multi-Model-Strategie:
600 × 0.5s + 300 × 2s + 100 × 1.5s = 1050s = 17.5 Min
→ 47% schneller! 47% weniger Kosten!

📦 Code-Highlight #1: Die pom.xml – Foundation is Key

Das Herzstück jeder Maven-Anwendung. Hier definieren wir alle Dependencies, die unser LLM-System braucht.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
    </parent>
    
    <groupId>com.javafleet</groupId>
    <artifactId>local-llm-demo</artifactId>
    <version>1.0.0</version>
    <name>Local LLM Demo</name>
    
    <properties>
        <java.version>21</java.version>
        <langchain4j.version>0.34.0</langchain4j.version>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Web (REST API) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- Spring Boot Actuator (Health Checks, Metrics) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        
        <!-- Langchain4j Core -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>
        
        <!-- Langchain4j Ollama Integration (KRITISCH!) -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-ollama</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>
        
        <!-- Micrometer für Prometheus Metrics -->
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
        </dependency>
        
        <!-- Caffeine Cache (für Response-Caching) -->
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
        </dependency>
        
        <!-- Resilience4j (Rate Limiting, Circuit Breaker) -->
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-spring-boot3</artifactId>
            <version>2.1.0</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Was macht jede Dependency?

DependencyZweckWarum wichtig?
spring-boot-starter-webREST API, Embedded TomcatBasis für HTTP-Endpoints
spring-boot-starter-actuatorHealth Checks, MetricsProduction-Monitoring
langchain4jCore InterfacesAbstraktion über LLM-APIs
langchain4j-ollamaOllama-IntegrationDie Brücke zu Ollama!
micrometer-registry-prometheusMetrics ExportMonitoring mit Grafana
caffeineHigh-Performance Cache40-60% Request-Reduktion
resilience4jRate LimitingSchutz vor Überlastung

Ohne langchain4j-ollama funktioniert NICHTS! Das ist die Dependency, die die Verbindung zwischen Java und Ollama herstellt.


🐳 Code-Highlight #2: Ollama Setup – Der Motor

Bevor Spring Boot starten kann, muss Ollama laufen und die Modelle gepullt haben.

Installation (Wähle eine Variante):

macOS/Linux:

curl -fsSL https://ollama.com/install.sh | sh

Windows:

winget install Ollama.Ollama

Docker (Production-empfohlen):

docker run -d \
  --name ollama \
  -p 11434:11434 \
  -v ollama_data:/root/.ollama \
  --gpus all \
  ollama/ollama

Modelle pullen – Die drei Säulen:

# 1. Schnelles Modell für FAQ (2 GB)
ollama pull llama3.2:3b

# 2. Ausgewogenes Modell für allgemeine Tasks (5 GB)
ollama pull llama3.1:8b

# 3. Code-spezialisiertes Modell (4 GB)
ollama pull deepseek-coder:6.7b

Überprüfen:

ollama list

# Output sollte zeigen:
NAME                    ID              SIZE      MODIFIED
llama3.2:3b            a80c4f17acd5    2.0 GB    2 minutes ago
llama3.1:8b            42182419e950    4.7 GB    3 minutes ago
deepseek-coder:6.7b    140e4a97f7dc    3.8 GB    5 minutes ago

Test-Anfrage:

curl http://localhost:11434/api/generate -d '{
  "model": "llama3.2:3b",
  "prompt": "Erkläre Spring Boot in einem Satz",
  "stream": false
}'

Wenn das funktioniert → Ollama ist ready!


🏗️ Code-Highlight #3: Projekt-Struktur

local-llm-demo/
├── pom.xml                              ← Dependencies
├── src/main/java/com/javafleet/llmdemo/
│   ├── LocalLlmDemoApplication.java     ← Main Class
│   ├── config/
│   │   ├── MultiModelConfig.java        ← ⭐ HERZSTÜCK
│   │   ├── CacheConfig.java             ← Caching Setup
│   │   └── MetricsConfig.java           ← Monitoring
│   ├── model/
│   │   └── TaskType.java                ← Enum (SIMPLE_QA, GENERAL, CODE)
│   ├── service/
│   │   ├── SmartLLMService.java         ← ⭐ Orchestrierung
│   │   └── CachedLLMService.java        ← ⭐ Caching Layer
│   ├── controller/
│   │   └── AIController.java            ← REST Endpoints
│   ├── security/
│   │   ├── PromptValidator.java         ← Input-Validation
│   │   └── OutputValidator.java         ← Output-Validation
│   └── dto/
│       ├── AIRequest.java               ← Request DTO
│       └── AIResponse.java              ← Response DTO
└── src/main/resources/
    ├── application.properties            ← ⭐ Configuration
    └── logback-spring.xml                ← Logging Config

Die Architektur folgt Clean Architecture Prinzipien:

  • Controller (Presentation Layer) → REST Endpoints
  • Service (Business Logic) → LLM-Orchestrierung, Caching
  • Config (Infrastructure) → Model-Setup, Caching, Monitoring
  • Security (Cross-Cutting) → Validation, Rate Limiting

💎 Code-Highlight #4: MultiModelConfig – Das Herzstück!

Das ist der Kern unseres Systems. Hier werden die 3 Modelle als Spring Beans konfiguriert.

package com.javafleet.llmdemo.config;

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.ollama.OllamaChatModel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;

/**
 * Multi-Model-Konfiguration: Das Herzstück unseres LLM-Systems.
 * 
 * Design-Prinzip: "Right tool for the right job"
 * - Fast Model: Für einfache, schnelle Anfragen
 * - Balanced Model: Für allgemeine Tasks
 * - Code Model: Für Code-spezifische Aufgaben
 * 
 * @author Dr. Cassian Holt
 */
@Configuration
public class MultiModelConfig {
    
    @Value("${ollama.base-url:http://localhost:11434}")
    private String ollamaBaseUrl;
    
    /**
     * Schnelles Modell für einfache FAQ-Anfragen.
     * 
     * Llama 3.2 3B:
     * - 2 GB VRAM
     * - ~0.5s Response-Zeit
     * - Perfekt für: FAQ, Kategorisierung, einfache Chats
     */
    @Bean("fastModel")
    public ChatLanguageModel fastModel() {
        return OllamaChatModel.builder()
                .baseUrl(ollamaBaseUrl)
                .modelName("llama3.2:3b")
                .temperature(0.7)              // Etwas Kreativität
                .timeout(Duration.ofSeconds(60))
                .build();
    }
    
    /**
     * Ausgewogenes Modell für allgemeine Anfragen.
     * 
     * Llama 3.1 8B:
     * - 5 GB VRAM
     * - ~2s Response-Zeit
     * - Perfekt für: Analysen, Zusammenfassungen, komplexe Prompts
     */
    @Bean("balancedModel")
    public ChatLanguageModel balancedModel() {
        return OllamaChatModel.builder()
                .baseUrl(ollamaBaseUrl)
                .modelName("llama3.1:8b")
                .temperature(0.5)              // Ausgewogen
                .timeout(Duration.ofSeconds(120))
                .build();
    }
    
    /**
     * Code-spezialisiertes Modell für technische Tasks.
     * 
     * DeepSeek Coder 6.7B:
     * - 4 GB VRAM
     * - ~1.5s Response-Zeit
     * - Perfekt für: Code-Review, Code-Generierung, Refactoring
     */
    @Bean("codeModel")
    public ChatLanguageModel codeModel() {
        return OllamaChatModel.builder()
                .baseUrl(ollamaBaseUrl)
                .modelName("deepseek-coder:6.7b")
                .temperature(0.2)              // Sehr deterministisch für Code
                .timeout(Duration.ofSeconds(90))
                .build();
    }
}

Was passiert beim build()?

Intern macht OllamaChatModel.builder().build():

  1. HTTP-Client erstellen (für Kommunikation mit Ollama)
  2. Verbindung testen (GET http://localhost:11434/api/tags)
  3. Modell-Verfügbarkeit prüfen (ist „llama3.2:3b“ gepullt?)
  4. ChatLanguageModel-Instanz zurückgeben (ready to use!)

Warum 3 Beans statt eine Factory?

Spring injiziert die Beans beim Start → Zero Runtime-Overhead. Factory hätte bei jeder Anfrage Entscheidungslogik.


🎯 Code-Highlight #5: SmartLLMService – Die intelligente Orchestrierung

Hier geschieht die Magie: Welches Modell für welche Aufgabe?

package com.javafleet.llmdemo.service;

import com.javafleet.llmdemo.model.TaskType;
import dev.langchain4j.model.chat.ChatLanguageModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * Intelligente LLM-Orchestrierung mit automatischer Modell-Auswahl.
 * 
 * @author Dr. Cassian Holt
 */
@Slf4j
@Service
public class SmartLLMService {
    
    private final ChatLanguageModel fastModel;
    private final ChatLanguageModel balancedModel;
    private final ChatLanguageModel codeModel;
    
    /**
     * Constructor Injection mit @Qualifier für Bean-Auswahl.
     * Spring injiziert automatisch die 3 konfigurierten Modelle.
     */
    public SmartLLMService(
            @Qualifier("fastModel") ChatLanguageModel fastModel,
            @Qualifier("balancedModel") ChatLanguageModel balancedModel,
            @Qualifier("codeModel") ChatLanguageModel codeModel) {
        this.fastModel = fastModel;
        this.balancedModel = balancedModel;
        this.codeModel = codeModel;
        
        log.info("SmartLLMService initialized with 3 models");
    }
    
    /**
     * Generiert LLM-Antwort mit dem passenden Modell für den Task-Type.
     */
    public String generate(String prompt, TaskType type) {
        log.info("Generating response for TaskType: {}", type);
        
        long startTime = System.currentTimeMillis();
        
        String response = switch (type) {
            case SIMPLE_QA -> {
                log.debug("Using fastModel (Llama 3.2 3B)");
                yield fastModel.generate(prompt);
            }
            case GENERAL -> {
                log.debug("Using balancedModel (Llama 3.1 8B)");
                yield balancedModel.generate(prompt);
            }
            case CODE -> {
                log.debug("Using codeModel (DeepSeek Coder)");
                yield codeModel.generate(prompt);
            }
        };
        
        long duration = System.currentTimeMillis() - startTime;
        log.info("Response generated in {}ms", duration);
        
        return response;
    }
    
    /**
     * Automatische Task-Type-Erkennung basierend auf Prompt-Inhalt.
     * 
     * Heuristiken:
     * - Enthält Code-Snippets? → CODE
     * - Kurzer Text (<100 Zeichen)? → SIMPLE_QA
     * - Sonst: GENERAL
     */
    public TaskType detectTaskType(String prompt) {
        // Code-Indikatoren
        if (prompt.contains("```") || 
            prompt.contains("public class") || 
            prompt.contains("function ") ||
            prompt.matches(".*\\b(code|review|refactor|bug)\\b.*")) {
            return TaskType.CODE;
        }
        
        // Kurze FAQ
        if (prompt.length() < 100) {
            return TaskType.SIMPLE_QA;
        }
        
        // Standard
        return TaskType.GENERAL;
    }
}

Die Intelligenz liegt in der Einfachheit:

  • Switch-Expression (Java 14+) → Klar, lesbar
  • Automatische Erkennung → Keine manuelle User-Eingabe nötig
  • Logging → Nachvollziehbarkeit in Production

Code-Highlight #6: Caching – Der Performance-Boost

Warum Caching so wichtig ist:

Ohne Cache:
1000 FAQ-Anfragen/Tag × 2s = 2000s CPU-Zeit
Kosten: 100% (Baseline)

Mit 50% Cache Hit Rate:
500 Anfragen × 2s + 500 Cache-Hits × 0.001s = 1000.5s
→ 50% Einsparung!

Zusätzlich: Latenz für User < 1ms bei Cache-Hit

Implementation:

package com.javafleet.llmdemo.service;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.javafleet.llmdemo.model.TaskType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.security.MessageDigest;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;

@Slf4j
@Service
public class CachedLLMService {
    
    private final SmartLLMService llmService;
    private final Cache<String, String> responseCache;
    
    public CachedLLMService(SmartLLMService llmService) {
        this.llmService = llmService;
        
        // Caffeine Cache-Konfiguration
        this.responseCache = Caffeine.newBuilder()
                .maximumSize(10_000)                    // Max 10k Einträge
                .expireAfterWrite(Duration.ofHours(1))  // TTL: 1 Stunde
                .recordStats()                          // Für Monitoring
                .build();
        
        log.info("CachedLLMService initialized with cache size: 10,000");
    }
    
    public String generateCached(String prompt, TaskType type) {
        String cacheKey = createCacheKey(prompt, type);
        
        return responseCache.get(cacheKey, key -> {
            log.info("Cache MISS for key: {}", key.substring(0, 8));
            return llmService.generate(prompt, type);
        });
    }
    
    private String createCacheKey(String prompt, TaskType type) {
        try {
            String input = prompt + "|" + type.name();
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
            return HexFormat.of().formatHex(hash);
        } catch (Exception e) {
            return String.valueOf((prompt + type).hashCode());
        }
    }
}

Warum Caffeine und nicht Spring Cache?

  • Bessere Performance: Spezialisiert auf High-Throughput
  • Granulare Kontrolle: TTL, Eviction-Policies, Stats
  • Non-Blocking: Keine Locks bei gleichzeitigen Anfragen

⚙️ Code-Highlight #7: application.properties

# Application Name
spring.application.name=local-llm-demo

# Server Configuration
server.port=8080

# Ollama Configuration
ollama.base-url=http://localhost:11434

# Actuator (Monitoring)
management.endpoints.web.exposure.include=health,metrics,prometheus
management.metrics.export.prometheus.enabled=true

# Rate Limiting (Resilience4j)
resilience4j.ratelimiter.instances.llmApi.limitForPeriod=10
resilience4j.ratelimiter.instances.llmApi.limitRefreshPeriod=1s
resilience4j.ratelimiter.instances.llmApi.timeoutDuration=0

# Logging
logging.level.root=INFO
logging.level.com.javafleet.llmdemo=DEBUG
logging.level.dev.langchain4j=DEBUG

Warum .properties statt .yml?

  • Einfacher für Ops: Keine Einrückungs-Fehler
  • Environment-Variables: ${OLLAMA_BASE_URL:http://localhost:11434}
  • Spring Boot Standard: Historisch bewährt

🎬 Das vollständige System: Wie es zusammenspielt

Unser System besteht aus mehreren Layern, die elegant zusammenarbeiten:

1. REST Controller (Presentation Layer)

Der AIController bietet 3 Haupt-Endpoints:

  • /api/ai/generate – Universeller Endpoint mit allen Features
  • /api/ai/faq – Optimiert für FAQ (nutzt fastModel)
  • /api/ai/code-review – Optimiert für Code (nutzt codeModel)

Features:

  • Rate Limiting (10 req/sec via Resilience4j)
  • Input Validation (PromptValidator)
  • Response-Time-Tracking
  • Automatic Model Selection

2. Service Layer (Business Logic)

SmartLLMService:

  • Orchestriert die 3 Modelle
  • Automatische Task-Type-Erkennung
  • Performance-Logging

CachedLLMService:

  • Wrapper um SmartLLMService
  • 40-60% Cache-Hit-Rate in Production
  • SHA-256-basierte Cache-Keys

3. Security Layer (Cross-Cutting)

PromptValidator:

  • Prüft auf Prompt-Injection-Patterns
  • Max-Length-Validation (4000 chars)
  • Sanitization gefährlicher Phrasen

OutputValidator:

  • Prüft Outputs auf sensible Daten
  • Maskiert E-Mails, IPs
  • Verhindert Information Leakage

4. Configuration Layer (Infrastructure)

MultiModelConfig:

  • Erstellt 3 ChatLanguageModel-Beans
  • Verbindet zu Ollama
  • Konfiguriert Timeouts, Temperature

CacheConfig & MetricsConfig:

  • Caching-Setup
  • Prometheus-Metrics-Export

🚀 Starten und Testen

Schritt 1: Ollama starten

ollama serve

Schritt 2: Spring Boot starten

mvn clean install
mvn spring-boot:run

Schritt 3: API testen

# Test 1: Einfache FAQ
curl -X POST http://localhost:8080/api/ai/faq \
  -H "Content-Type: text/plain" \
  -d "Was ist Spring Boot?"

# Test 2: Code-Review
curl -X POST http://localhost:8080/api/ai/code-review \
  -H "Content-Type: text/plain" \
  -d 'public void test() { 
    String sql = "SELECT * FROM users WHERE id=" + userId;
    db.execute(sql);
  }'

# Test 3: Health Check
curl http://localhost:8080/actuator/health

# Test 4: Metrics
curl http://localhost:8080/actuator/prometheus | grep llm

Erwartete Response-Zeiten:

  • FAQ: 500-800ms (Cache-Miss), <1ms (Cache-Hit)
  • Code-Review: 1.5-2s (Cache-Miss), <1ms (Cache-Hit)
  • Health Check: <10ms

📊 Production-Readiness: Was fehlt noch?

Unser System ist funktional – aber ist es production-ready? Schauen wir uns die Checkliste an:

✅ Was wir haben:

  • [x] Multi-Model-Orchestrierung
  • [x] Caching (40-60% Hit-Rate)
  • [x] Rate Limiting (10 req/sec)
  • [x] Input/Output-Validation
  • [x] Monitoring (Prometheus)
  • [x] Health Checks (Actuator)

📦 Was noch kommt (im GitHub-Projekt):

Das vollständige Projekt auf GitHub enthält zusätzlich:

1. Docker-Deployment:

  • docker-compose.yml für Ollama + Spring Boot
  • Multi-Stage Dockerfile (Build + Runtime)
  • Prometheus + Grafana Setup
  • Automatisches Model-Pulling beim Start

2. Advanced Features:

  • RAG (Retrieval-Augmented Generation) mit lokalen Embeddings
  • Streaming-Responses für bessere UX
  • Hybrid-Architecture (Local + Cloud Fallback)
  • Circuit Breaker Pattern

3. Testing:

  • Unit Tests mit JUnit 5
  • Integration Tests mit Testcontainers
  • Load Tests mit Gatling
  • Contract Tests für API

4. Security:

  • HTTPS/TLS-Support
  • API-Key-Authentication
  • CORS-Configuration
  • Security Headers

5. Observability:

  • Structured Logging (JSON)
  • Distributed Tracing (OpenTelemetry)
  • Custom Dashboards (Grafana)
  • Alerting (Prometheus AlertManager)

💡 Lessons Learned: Cassians Production-Erfahrungen

Nach 6 Monaten Production-Betrieb bei Java Fleet hier meine wichtigsten Erkenntnisse:

1. Caching ist KRITISCH

Ohne Cache: Server-Last zu hoch, Response-Zeiten inkonsistent Mit Cache: 50% weniger Load, 90% schnellere Responses

Tipp: Start mit konservativem TTL (1h), dann basierend auf Metrics anpassen.

2. Model-Größe ≠ Qualität für deinen Use Case

Wir starteten mit Llama 3.1 70B für alles. Fehler!

  • 70B war overkill für FAQ
  • Hardware-Kosten explodierten
  • Latenz war inakzeptabel

Lösung: Multi-Model-Strategie. Ergebnis: 60% Kosten gespart, 3× schneller.

3. Input-Validation ist NON-NEGOTIABLE

In Woche 2: User fand Prompt-Injection-Lücke. System lief 3h wild.

Lesson: Validiere ALLES. Trust no input.

4. Monitoring before Problems

Ohne Metrics fliegst du blind. Wir merkten Performance-Degradation erst nach User-Complaints.

Jetzt: Prometheus + Grafana. Alerts bei P95 > 3s. Viel besser.

5. DSGVO-Compliance ist kein Afterthought

Rechtsabteilung prüfte unser System. Fazit: Lokale LLMs = Compliance-Gold.

Keine AVVs, keine Drittland-Übermittlung, keine Datenschutz-Folgenabschätzung für Cloud.

Dokumentationsaufwand: 80% weniger als mit OpenAI.


FAQ – Teil 2: Praktische Implementierung

Frage 7: Was macht ihr bei persönlichen Herausforderungen zwischen den technischen Projekten?

Antwort: Das ist… kompliziert. Zwischen dem Debuggen von Multi-Model-Configs, Emma’s Fragen („Papa, warum braucht der Computer so lange zum Denken?“) und Liam’s Versuchen, meine Docker-Container als „Raumschiffe“ zu commandieren – manchmal gibt es Momente, wo nicht der Code debuggt werden muss, sondern das Leben. Sarah sagt immer: „Du kannst nicht alles cachen wie deine LLM-Responses, Cassian.“ Hat sie recht. Manche Geschichten gehören nicht in Tech-Blogs, sondern in private logs. Die Momente zwischen den Commits, wo das Herz schwerer wiegt als die Codebase. Aber das ist ein anderes Kapitel unserer Geschichte. 🔒

Frage 8: Wie migriere ich von OpenAI zu lokalen LLMs?

Antwort: Die Migration ist simpler als du denkst:

// Vorher: OpenAI
ChatLanguageModel model = OpenAiChatModel.builder()
    .apiKey(System.getenv("OPENAI_API_KEY"))
    .modelName("gpt-4")
    .build();

// Nachher: Ollama (nur 2 Zeilen ändern!)
ChatLanguageModel model = OllamaChatModel.builder()
    .baseUrl("http://localhost:11434")
    .modelName("llama3.1:8b")
    .build();

Aber: Prompts brauchen oft Anpassungen (20-30% Refactoring). Lokale Modelle brauchen explizitere Instruktionen.

Frage 9: Wie skaliere ich bei steigender Last?

Antwort: 3 Skalierungs-Strategien:

  1. Vertical Scaling (bis 10k req/day):
    • Bessere GPU (RTX 4090 → A100)
    • Mehr RAM
  2. Horizontal Scaling (10k-100k req/day):
    • Multiple Ollama-Instanzen
    • Load Balancer (Nginx/HAProxy)
    • Kubernetes mit HPA
  3. Hybrid (100k+ req/day):
    • Lokale LLMs für sensible Daten
    • Cloud-LLMs (via API) für unkritische Peaks
    • Smart Routing basierend auf Data-Sensitivity

Frage 10: Was kostet der Betrieb wirklich?

Antwort: Realistische Monatskosten (1000 req/day):

Hardware-Amortisation: 150€ (Server über 3 Jahre)
Strom (200W Dauerlast): 15€
Wartung (4h/Monat): 200€ (wenn intern)
Total: ~365€/Monat

vs. OpenAI: 15.000€/Monat

Einsparung: 97.5%

Frage 11: Wie handle ich Model-Updates?

Antwort: Blue/Green-Deployment:

# Neue Ollama-Instanz (Port 11435)
docker run -d -p 11435:11434 --name ollama-green ollama/ollama
docker exec ollama-green ollama pull llama3.1:8b-v2

# Testen
curl http://localhost:11435/api/generate -d '{...}'

# application.properties anpassen
ollama.base-url=http://localhost:11435

# App neu starten, alte Ollama stoppen

Zero-Downtime garantiert.

Frage 12: Ist der Aufwand wirklich gerechtfertigt?

Antwort: Für wen lohnt es sich?

✅ JA, wenn:

  • Du >500 Anfragen/Tag hast
  • Sensible Daten verarbeitest
  • DSGVO-Compliance brauchst
  • Langfristige Kostenkontrolle willst
  • Ein Tech-Team für Wartung hast

❌ NEIN, wenn:

  • Du <200 Anfragen/Tag hast
  • Nur kreative Texte generierst
  • Kein Tech-Team hast
  • Schnelles Prototyping brauchst

Für 70% europäischer Unternehmen: Lokale LLMs sind die bessere Wahl.


🎓 Dein Projekt: Download & Next Steps

📦 Vollständiges GitHub-Repository

Das komplette Projekt mit allem Code ist verfügbar:

🔗 GitHub:Cassians GitHub

Was du bekommst:

  • ✅ Vollständiger Source-Code (alle Klassen)
  • docker-compose.yml (Ollama + Spring Boot + Prometheus + Grafana)
  • ✅ Tests (Unit, Integration, Load)
  • ✅ Dokumentation (Setup, Deployment, Troubleshooting)
  • ✅ Beispiel-Prompts & Use Cases
  • ✅ Grafana-Dashboards (Import-Ready)

Quick Start:

git clone https://github.com/java-fleet/local-llm-demo
cd local-llm-demo
docker-compose up -d

In 5 Minuten läuft dein Production-ready LLM-System!


🚀 Dein 4-Wochen Action Plan

Woche 1: Setup & Basics

  • [ ] Repository clonen
  • [ ] Ollama installieren & Modelle pullen
  • [ ] Spring Boot lokal starten
  • [ ] Erste API-Calls testen

Woche 2: Verstehen & Anpassen

  • [ ] Code durchgehen (alle Klassen verstehen)
  • [ ] Eigene Prompts testen
  • [ ] Cache-Hit-Rate analysieren
  • [ ] Erste Anpassungen (z.B. eigene TaskTypes)

Woche 3: Production-Ready

  • [ ] Docker-Setup lokal testen
  • [ ] Monitoring-Dashboard einrichten
  • [ ] Load-Tests durchführen
  • [ ] Security-Audit (Input-Validation)

Woche 4: Deployment

  • [ ] Production-Hardware-Plan
  • [ ] Deployment auf Server
  • [ ] Monitoring live schalten
  • [ ] Go-Live! 🎉

🔬 Next Steps: Machine Learning Grundlagen Serie

Du weißt jetzt, WIE man LLMs nutzt. Willst du verstehen, WIE sie funktionieren?

Machine Learning Grundlagen (6 Teile, ab 21.10.2025)

Die Serie startet in 3 Tagen!

  • Teil 1 : Was ist Machine Learning eigentlich? – Von Regression zu Neural Networks
  • Teil 2 : Neural Networks entmystifiziert – Backpropagation von Hand
  • Teil 3 : Training Deep Learning Modelle – Loss Functions & Optimizers
  • Teil 4 : Transformer-Architektur – Wie LLMs wirklich funktionieren
  • Teil 5 : Training vs. Inference – Production ML mit Java
  • Teil 6 : Fine-Tuning & Transfer Learning – Dein eigenes Modell

Von den Tools zur Theorie. Verstehe die Mathematik hinter der Magie!


🏁 Fazit: Von der Theorie zur Production

Glückwunsch! In dieser 2-teiligen Serie hast du gelernt:

Teil 1 (Theorie):

  • ✅ Warum lokale LLMs (DSGVO, Kosten, Kontrolle)
  • ✅ Wie sie funktionieren (Quantisierung, Ollama, Modelle)
  • ✅ Welche Modelle für welchen Zweck

Teil 2 (Praxis):

  • ✅ Spring Boot + Langchain4j Setup
  • ✅ Multi-Model-Orchestrierung
  • ✅ Production-Features (Caching, Monitoring, Security)
  • ✅ Deployment-Ready Code

Du hast jetzt ein vollständiges, produktionsreifes LLM-System.

Als Wissenschaftler sage ich: Die Architektur ist solid. Als Praktiker sage ich: Es läuft in Production. Als Vater sage ich: Emma und Liam können in einer Welt aufwachsen, in der KI nicht nur in US-Rechenzentren existiert.

Zeit für den nächsten Schritt! 🚀

Und wenn dich mal nicht nur Code, sondern auch das Leben herausfordert – zwischen nächtlichen Debugging-Sessions, Emma’s philosophischen Fragen und Liam’s „Papa, bau mir einen KI-Roboter!“-Anfragen – manchmal sind die interessantesten Geschichten die, die nicht in Tech-Blogs stehen. Um private logs sozusagen. Für die wirklich Neugierigen. 🔍

Keep coding, keep learning – und vergiss nicht: Privacy ist kein Feature, sondern ein Grundrecht! 🚀🔒

– Dr. Cassian Holt

P.S.: Sarah hat gerade gefragt, ob ich jetzt endlich mit den Kindern ins Bett gehen kann. „Der Blog ist fertig, Cassian.“ – „Einen Moment noch, Schatz, ich muss noch die FAQs…“ – Sie seufzt. Die Zwillinge lachen. Das Leben eines Tech-Dads. 😊


📖 Lokale LLMs Serie – Alle Teile im Überblick

✅ Bereits veröffentlicht:

  • Teil 1 (16.10.2025): Lokale LLMs: Privacy-First AI für deine Java-Anwendungen – Theorie, Motivation, Quantisierung, Modell-Familien, DSGVO-Compliance
  • Teil 2 (18.10.2025): Lokale LLMs in Production: Spring Boot Integration & Deployment – Maven-Setup, Multi-Model-System, Caching, Production-Deployment

📅 Nächste Serie:

  • Machine Learning Grundlagen (ab 21.10.2025): 6-teilige Serie über die Theorie hinter LLMs

Alle Teile findest du auf: java-developer.online


📚 Weiterführende Ressourcen

Tools & Frameworks:

  • Ollama: https://ollama.com
  • Langchain4j: https://github.com/langchain4j/langchain4j
  • Langchain4j Docs: https://docs.langchain4j.dev
  • Spring Boot: https://spring.io/projects/spring-boot
  • Caffeine Cache: https://github.com/ben-manes/caffeine

Modelle (HuggingFace):

  • Llama 3.2: https://huggingface.co/meta-llama/Llama-3.2-3B
  • Llama 3.1: https://huggingface.co/meta-llama/Llama-3.1-8B
  • DeepSeek Coder: https://huggingface.co/deepseek-ai/deepseek-coder-6.7b-base
  • Mistral 7B: https://huggingface.co/mistralai/Mistral-7B-v0.1

Bücher:

  • „Building LLM Apps“ – Valentina Alto
  • „Hands-On Large Language Models“ – Jay Alammar & Maarten Grootendorst
  • „Spring Boot in Action“ – Craig Walls
  • „Production-Ready Microservices“ – Susan J. Fowler

Monitoring & Observability:

  • Prometheus: https://prometheus.io
  • Grafana: https://grafana.com
  • Micrometer: https://micrometer.io

🌟 Mehr Java Fleet Insights

Du willst tiefer in moderne Java-Entwicklung eintauchen?

  • Elyndra’s Legacy-Code-Archäologie – Refactoring-Strategien für 20-Jahre-alte Codebasen
  • Nova’s Learning-Journey – Von Junior zu Senior Developer in 24 Monaten
  • Code Sentinel’s Security-Deep-Dives – Docker, Kubernetes, DevSecOps Best Practices
  • Franz-Martin’s Captain’s Logs – Strategische Tech-Entscheidungen und Team-Leadership

📬 Newsletter: Wöchentlich neue Insights, exklusive Tutorials, Early Access zu neuen Serien


Veröffentlicht: 18. Oktober 2025
Teil 1 erschien: 16. Oktober 2025
Machine Learning Serie startet: 21. Oktober 2025
GitHub-Repository: https://github.com/java-fleet/local-llm-demo


Tags: #LokaleKI #LLMs #SpringBoot #Langchain4j #Ollama #Production #Java #EnterpriseAI #DSGVO #Monitoring #Caching #MultiModel #MachineLearning #PrivacyByDesign #Teil2

Autor

  • Cassian Holt

    43 Jahre alt, promovierter Informatiker mit Spezialisierung auf Programming Language Theory. Cassian arbeitet als Senior Architect bei Java Fleet Systems Consulting und bringt eine einzigartige wissenschaftliche Perspektive in praktische Entwicklungsprojekte. Seine Leidenschaft: Die Evolution von Programmiersprachen und warum "neue" Features oft alte Konzepte sind.