JPMS & Agentic AI: Wie echte Modularisierung autonome Coding-Agenten beherrschbar macht
Autonome KI-Coding-Agenten wie Claude Code, OpenAI Codex/Canvas, Google Gemini oder Junie revolutionieren die Softwareentwicklung. Sie schreiben Code in atemberaubender Geschwindigkeit, refaktorisieren nicht nur Details innerhalb einzelner Klassen, sondern vollständige Teilsysteme und lösen komplexe Aufgabenstellungen über ganze Repositories hinweg.
Doch mit dieser Geschwindigkeit wächst ein fundamentales, oft übersehenes Problem: Agenten beschleunigen nicht nur die Feature-Entwicklung, sondern, wenn man nicht aufpasst, auch die Architekturerosion. Ohne harte technische Leitplanken verwandeln KI-Agenten selbst gut entworfene Architekturen im Zeitraffer in einen unentwirrbaren Big Ball of Mud.
Im vorherigen Beitrag zur modularen Softwareentwicklung mit Java wurde dargelegt, warum unzureichende Modularisierung und unkontrollierte interne Abhängigkeiten menschliche Entwickler überfordern. Im Zeitalter agentischer KI wird Modularisierung – und speziell das Java Platform Module System (JPMS) – zu weit mehr als einem reinen Ordnungsprinzip: Es wird zur essenziellen Steuerungs- und Sicherheitsarchitektur für autonome KI-Agenten.
1. Die Anatomie des Agenten-Problems
Warum geraten KI-gestützte Entwicklungsprojekte ohne strikte Modulgrenzen so schnell aus den Fugen? Die Ursachen liegen oft in der grundlegenden Funktionsweise von Large Language Models (LLMs):
Token-Limits und das Lost in the Middle-Phänomen
LLMs verarbeiten Informationen über ein begrenztes Kontextfenster (Context Window). Selbst Modelle mit 100k+ oder gar Millionen Token Kontext leiden unter zwei Effekten: 1. Latenz und Kosten: Je größer der Prompt (bestehend aus Quellcode, Dokumentation und Historie), desto teurer und langsamer wird jeder Ausführungsschritt des Agenten. 2. Aufmerksamkeitsverlust (Lost in the Middle): Relevante Details, Architekturregeln oder Randbedingungen gehen im Rauschen unzähliger Code-Zeilen schlicht unter.
Der Weg des geringsten Widerstands
Ein KI-Agent optimiert lokal auf die Erfüllung des Prompts: "Implementiere Feature X" oder "Behebe Fehler Y". Wenn eine Hilfsmethode, ein internes Entity oder ein Datenzugriffsobjekt in einem anderen Paket existiert, wird der Agent diese Klasse schlicht importieren und verwenden.
Abb. 1: Der Weg des geringsten Widerstands – Monolithischer Wildwuchs vs. erzwungene Modulgrenzen mit JPMS
Menschliche Entwickler verletzen Architekturkonventionen meist unter Zeitdruck; KI-Agenten tun es mangels globaler Systemübersicht und weil sie den kürzesten syntaktischen Lösungsweg wählen.
Exkurs & Einwand: Warum greift package-private nicht und warum ist die Wiederverwendung interner Helfer riskant?
Bei der Betrachtung von unberechtigten Querimporten drängen sich in der Praxis zwei fundamentale Fragen auf: #### 1. Das Sichtbarkeits-Dilemma: Warum ist die interne Klasse überhaupt `public`? Liegt eine Klasse in einem internen Paket (z. B. `de.ruu.lib.jpa.core.internal.InternalQueryOptimizer`), stellt sich die Frage: Warum wird sie nicht einfach *package-private* (ohne Zugriffsmodifizierer) deklariert, sodass externe Klassen sie gar nicht erst importieren können? * **Das All-or-Nothing-Problem:** Besteht eine Bibliothek aus mehreren zusammengehörigen Paketen (z. B. `core`, `query`, `mapping`), müssen Klassen, die paketübergreifend innerhalb der Bibliothek genutzt werden sollen, zwingend als `public` deklariert werden. * **Ungewollte globale Exposition:** Im klassischen Java (flacher Classpath vor Java 9) gab es keine Zwischenstufe: Jede `public`-Klasse war automatisch für das **gesamte System** und alle Konsumenten sichtbar. * *Ausführliche Herleitung:* Wie dieses [Sichtbarkeits-Dilemma in Java vor Version 9](../modular-software-in-java/index.md#kapselung-durch-packages) unweigerlich zu Architekturerosion führt, wird im vorherigen Beitrag detailliert anhand von [Abb. 6 (All-or-Nothing-Sichtbarkeit)](../modular-software-in-java/index.md#kapselung-durch-packages) demonstriert. Erst **JPMS** löst dieses Dilemma auf: Eine Klasse kann `public` sein (und damit modulweit über interne Paketgrenzen hinweg nutzbar), bleibt aber für alle externen Module und Konsumenten unsichtbar, solange ihr Paket in `module-info.java` nicht exportiert wird. --- #### 2. Das Wiederverwendungs-Missverständnis: Warum dürfen "nützliche" interne Funktionen nicht einfach konsumiert werden? Bietet eine Klasse wie `InternalQueryOptimizer` eine hochgradig nützliche Funktionalität, könnte eingewendet werden: Ist es nicht effizient, wenn der KI-Agent diese Logik direkt wiederverwendet, statt redundanten Code zu erzeugen? Aus einer System- und Lebenszyklusperspektive übersteigen die versteckten Risiken den kurzfristigen Nutzen bei weitem: * **Umgehung von Invarianten & Schutzmechanismen:** Interne Helfer sind auf interne Abläufe optimiert. Sie verzichten häufig bewusst auf Validierungen von Vorbedingungen, Berechtigungsprüfungen, Transaktionsgrenzen oder Thread-Sicherheit, weil sie davon ausgehen, dass die aufrufende Komponente diese Garantien bereits sicherstellt. Ein externer Direktaufruf führt zu subtilen Laufzeitfehlern oder Dateninkonsistenzen. * **Explosion des Explosionsradius (*Blast Radius*):** Wird ein internes Implementierungsdetail von Konsumenten direkt referenziert, verliert das Ursprungsmodul seine Refaktorisierungsfreiheit. Jede interne Code-Optimierung wird unweigerlich zum *Breaking Change* für das Gesamtsystem. * **Der architektonisch saubere Weg:** Wenn eine Funktionalität allgemeinen Wert besitzt, darf sie nicht über illegitime Abkürzungen konsumiert werden. Die saubere Lösung besteht darin: 1. Die Methode formal als stabilen Vertrag in die öffentliche Schnittstelle des Moduls aufzunehmen (`exports`), oder 2. die Funktionalität in eine eigenständige, wiederverwendbare Hilfsbibliothek (z. B. `lib.util`) mit eigenem Moduldeskriptor und dedizierten Tests auszulagern.
2. Warum Konventionen für KI nicht genügen — Der Fall für JPMS
Vor Java 9 basierte Architekturtreue in Java im Wesentlichen auf Disziplin und Namenskonventionen (z. B. Pakete mit .internal. im Namen).
Für KI-Agenten sind reine Konventionen jedoch wirkungslos: - Konventionen sind soft: Ein Agent ignoriert Kommentare oder Namensregeln, wenn sie nicht im unmittelbaren Fokus stehen. - Tools wie Checkstyle oder Linter kommen oft zu spät: Häufig prüfen statische Analysetools erst im CI-Build – bis dahin hat der Agent bereits Dutzende voneinander abhängige Änderungen auf einem falschen Fundament aufgebaut.
Strong Encapsulation als unüberwindbare Barriere
Das Java Platform Module System bietet mit seiner strikten Kapselung (Strong Encapsulation) eine sprach- und runtime-erzwungene Barriere.
In einem modularen System existiert das Konzept der "öffentlich zugänglichen, aber eigentlich internen Klasse" nicht mehr für Modulgrenzen. Was nicht per module-info.java exportiert wird, ist für den Compiler und die JVM schlicht unsichtbar:
// Modul: de.ruu.lib.jpa.core
module de.ruu.lib.jpa.core {
// Nur öffentliche APIs und Contracts werden exportiert
exports de.ruu.lib.jpa.core;
exports de.ruu.lib.jpa.core.model;
// Interne Implementierungsdetails bleiben verborgen!
// package de.ruu.lib.jpa.core.internal wird NICHT exportiert.
}
Versucht ein Agent in einem abhängigen Modul eine interne Klasse zu importieren:
// Modul: de.ruu.app.pragma.core
package de.ruu.app.pragma.core;
import de.ruu.lib.jpa.core.internal.InternalQueryOptimizer; // FEHLER!
Schlägt der Java-Compiler sofort und unmissverständlich fehl:
[ERROR] Package de.ruu.lib.jpa.core.internal is declared in module de.ruu.lib.jpa.core,
which does not export it to module de.ruu.app.pragma.core
Der Agent wird durch die harte Rückmeldung des Compilers unmittelbar gezwungen, eine architekturkonforme Lösung über die öffentliche API zu finden.
3. Die 4 Säulen modularer KI-Steuerung
Wie genau unterstützt JPMS den agentischen Entwicklungs-Workflow? Die Vorteile lassen sich in vier Kernsäulen gliedern:
Abb. 2: Die 4 Säulen modularer KI-Steuerung im Überblick
1. Kontextfenster-Ökonomie & präzise Prompts
In einem klar modularisierten System muss der Agent nicht das gesamte Projekt laden. Soll eine Funktionalität im Modul app.pragma.core angepasst werden, umfasst der Kontext:
- Den Quellcode von app.pragma.core
- Die module-info.java des Moduls
- Die exportierten Schnittstellen der deklarierten Abhängigkeiten (requires)
Dies reduziert die Token-Menge oft um 70 bis 90 %. Das Ergebnis sind schnellere Antwortzeiten, geringere API-Kosten und signifikant höhere Lösungsqualität durch maximale Informationsdichte.
2. Begrenzung des Explosionsradius (Blast Radius)
Wenn ein Agent autonom Code umschreibt, besteht im Monolithen stets die Gefahr unbemerkter Seiteneffekte in fernen Systemteilen.
Mit JPMS ist der Blast Radius physikalisch begrenzt:
- Änderungen an internen Klassen eines Moduls können keine Konsumenten brechen, da niemand von außen darauf zugreifen kann.
- Bricht der Agent einen öffentlichen Vertrag (exports), meldet der Compiler den Fehler sofort in allen abhängigen Modulen.
3. Explizite Verträge als Handlungsrahmen (Contracts as Prompts)
Moduldeskriptoren und Schnittstellen definieren glasklare Spielregeln:
module de.ruu.app.pragma.frontend.fx {
requires transitive de.ruu.app.pragma.core;
requires de.ruu.lib.fx;
// Erlaubt Reflection nur für den CDI-Container / FXML
opens de.ruu.app.pragma.fx.task.view to org.jboss.weld.se.core, javafx.fxml;
}
Die module-info.java ist für den Agenten wie ein maschinenlesbarer Vertrag. Sie sagt dem LLM exakt:
- Welche Abhängigkeiten stehen zur Verfügung?
- Welche Pakete dürfen konsumiert werden?
- Wo ist Reflection (z. B. für CDI oder UI-Bindings) explizit erlaubt und wo untersagt?
Exkurs: Der fundamentale Unterschied zwischen requires und requires transitive
In JPMS-Moduldeskriptoren steuert das Schlüsselwort `transitive`, wie Abhängigkeiten an konsumierende Module weitergegeben werden (*Implied Readability*): * **`requires
Abb. 3: Kapselung (requires) vs. Implied Readability (requires transitive) im Kontext von KI-Agenten
4. Schnelle, deterministische Feedbackschleifen (Automated Guardrails)
Autonome Agenten arbeiten iterativ: Code generieren → Testen → Fehler analysieren → Korrigieren.
In einem Monolithen dauert ein vollständiger Build- und Testzyklus oft viele Minuten. Bei kleinen, isolierten Modulen dauert der Durchlauf von Compiler und Unit-Tests wenige Sekunden. Der Agent erhält sofortiges Feedback und kann sich in kurzen Zyklen selbst korrigieren.
4. Architektur-Muster in der Praxis: Das r-uu-java-Modell
Wie sieht eine solche modulare Architektur in der Praxis aus? Im Referenzprojekt r-uu-java (Basis für die Aufgaben- und Projektmanagement-Software Pragma) setzt die Struktur konsequent auf wiederverwendbare Bibliotheken (lib/*) und getrennte Anwendungsmodule (app/*):
r-uu-java/
├── lib/ Wiederverwendbare Basiskomponenten
│ ├── bom/ Zentrales Bill of Materials (Dependency Management)
│ ├── archunit/ Automatisierte Architektur- & JPMS-Prüfungen
│ ├── cdi/ JPMS-konforme CDI-Infrastruktur
│ ├── jpa/ Persistenz-Verträge & Implementierung
│ └── fx/ JavaFX-Hilfsstrukturen & Steuerelemente
└── app/
└── pragma/ Fachliche Applikation
├── bom/ Applikationsspezifisches BOM
├── core/ Domänenmodell & Geschäftslogik
├── backend/ Jakarta EE / REST Services
└── frontend/fx/ JavaFX Desktop Client
Abb. 4: Mehrstufige Guardrails im Referenzsystem r-uu-java & Pragma
Mehrstufige Absicherung
Neben dem JPMS-Compiler greifen mehrstufige, automatisierte Schutzkreise:
- BOM-Ketten & Version Enforcer: Das
maven-enforcer-plugin(RequireSameVersions) stellt sicher, dass alle Module kompatible Versionen nutzen. - ArchUnit & JPMS Compliance Tests: Ein dediziertes Testmodul (
lib/archunit) verifiziert kontinuierlich: - Keine zyklischen Abhängigkeiten zwischen Paketen.
- Keine Split-Packages über Modulgrenzen hinweg.
- Saubere Trennung von API- und Implementierungspaketen.
- CDI- & Reflection-Bootstrap-Tests: Im Frontend stellt der
PragmaCdiBootstrapTestsicher, dass alle notwendigenopens ... to-Klauseln für die Laufzeit-Dependency-Injection vorhanden sind.
5. Fazit & Ausblick
Autonome KI-Coding-Agenten sind keine Bedrohung für saubere Softwarearchitektur – vorausgesetzt, die Architektur gibt die Leitplanken vor.
Wer glaubt, im KI-Zeitalter auf Modularisierung verzichten zu können, wird erleben, wie Agenten die technische Schuld schneller anhäufen, als Teams sie abtragen können. JPMS verwandelt Modularisierung von einer unverbindlichen Empfehlung in ein unumstößliches Regelwerk.
Die Vorteile auf einen Blick:
- Niedrigere Token-Kosten & schnellere Antworten durch fokussierte Kontextfenster.
- Keine ungewollten Querabhängigkeiten dank Strong Encapsulation.
- Hohe Zuverlässigkeit durch kurze, isolierte Test- und Feedbackschleifen.
- Sichere Refaktorisierungen mit klar begrenztem Blast Radius.
Ein nachfolgender Beitrag (JPMS in Aktion) zeigt an konkreten Beispielen aus dem PoC Pragma, wie sich eine vollmodulare JavaFX- und Jakarta-EE-Anwendung mit JPMS, Weld-SE und ArchUnit im täglichen agentischen Entwicklungsalltag bewährt.