1. Manueller Stack-Aufbau — ein schlechter Start
Wenn du bisher hauptsächlich Konsolen-Java-Anwendungen geschrieben hast, sah eine Abhängigkeit meist einfach aus: „Ich brauche eine Bibliothek — also binde ich sie ein." Bei Webanwendungen, auch kleinen, hört das schnell auf zu funktionieren: Für ein einziges „Feature" brauchst du Dutzende von Jars, wobei ein Teil davon nicht direkt für dich, sondern für die Infrastruktur nötig ist. Und schon baust du kein Programm mehr, sondern zusammen Lego ohne Bild auf der Schachtel.
Stell dir vor, du willst „einfach einen HTTP-Service aufstarten und JSON zurückgeben." Manuell bedeutet das: Web-Framework auswählen, Server (Tomcat/Jetty/Undertow), JSON-Bibliothek, Logging, einen Satz Spring-Module — und das alles sorgfältig versionskompatibel machen. In diesem Moment denken viele Einsteiger, „Spring ist kompliziert." Dabei wird hier nicht das Programmieren kompliziert, sondern der manuelle Stack-Aufbau.
Spring Boot löst diesen Schmerz nicht damit, „Abhängigkeiten zu verstecken", sondern durch praktische und sinnvolle Einstiegspunkte. Der erste solche Einstiegspunkt ist der Starter.
2. Was ein Starter in Spring Boot ist
Das Wort Starter im Namen einer Abhängigkeit klingt so, als würde man dir „eine fertige Anwendung in der Box" geben. In Wirklichkeit ist es einfacher und zuverlässiger: Ein Starter ist eine Abhängigkeit, die einen Satz anderer Abhängigkeiten für ein bestimmtes Szenario mitbringt. Normalerweise enthält der Starter selbst kaum Anwendungscode. Seine Aufgabe ist es, dem Build zu sagen: „Ich brauche diesen Stack."
In Gradle sieht das maximal unspektakulär aus — wie eine normale Zeile in dependencies:
dependencies {
// Wir binden nicht „eine Bibliothek" ein, sondern ein ganzes Szenario (Web MVC auf Servlet-Stack)
implementation("org.springframework.boot:spring-boot-starter-webmvc")
}
Und hier passiert ein wichtiger psychologischer Wechsel. Du hast nicht „eine Bibliothek webmvc eingebunden." Du hast eine Absicht erklärt: „Meine Anwendung wird eine servlet-basierte MVC-Webanwendung auf Spring sein." Als Antwort lädt Gradle nicht ein Artefakt herunter, sondern einen ganzen Graphen transitiver Abhängigkeiten. Fürs Erste reicht es, im Hinterkopf zu behalten: Hinter einer Zeile steckt ein ganzer Ast von Bibliotheken.
Hier ist es leicht, Starter und BOM aus der letzten Lektion zu verwechseln. BOM beantwortet die Frage „Welche Versionen sind kompatibel", der Starter die Frage „Welche Bibliotheken braucht man typischerweise für dieses Szenario." Das sind verschiedene Rollen, und zusammen erzielen sie einen sehr starken Effekt: Du wählst ein Szenario mit einer Zeile und bekommst einen abgestimmten Bibliothekssatz ohne manuelle Versionsauswahl.
3. Curated Dependency Set und „Teileset"
Das Wort curated in der IT bedeutet normalerweise „ausgewählt und geprüft." In unserem Kontext ist ein Curated Dependency Set ein Satz von Abhängigkeiten, der bereits für dich so zusammengestellt wurde, dass er logisch ist und in den meisten Projekten standardmäßig funktioniert. Das ähnelt einem Möbel-Bausatz: Du musst nicht separat 32 Schrauben, 4 Beine und einen Inbusschlüssel kaufen — du nimmst die Box, in der alles schon passt und zusammengehört.
Übertragen auf Spring-Boot-Projekte ist der Starter „die Box für ein Szenario." Einige Beispiel-Szenarien, die wir im catalog-service im Laufe des Projekts begegnen werden:
| Szenario (in einfachen Worten) | Wie es in Gradle ausgedrückt wird | Was du von der Plattform eigentlich „anforderst" |
|---|---|---|
| „Ich brauche einen Web-Service auf MVC" | spring-boot-starter-webmvc | MVC, JSON-Konvertierung, eingebetteter Server und grundlegende Web-Infrastruktur |
| „Ich brauche Diagnose zur Laufzeit" | spring-boot-starter-actuator | Actuator-Endpoints, Integrationen für Health/Info und Diagnose-Grundgerüst |
| „Ich brauche einen ordentlichen Test-Stack" | spring-boot-starter-test | JUnit 6 + Spring Test + praktische Test-Utilities in abgestimmten Versionen |
Beachte: In diesen Zeilen gibt es keine Auflistung von Low-Level-Modulen. Das ist normal. Ein gutes build.gradle.kts ist keine Liste aller Schrauben, sondern eine Liste der Fähigkeiten, die das Projekt benötigt.
Und noch eins: Ein Starter ist keine „Magie." Er ist einfach eine Abhängigkeit, die sich in andere Abhängigkeiten entfaltet. Magie entsteht erst, wenn du aufhörst hinzuschauen, was genau eingebunden wurde, und erwartest, dass es „selbst alles erledigt." Das werden wir nicht tun: Wir werden verstehen, was passiert — einfach auf einem verständlichen Level.
4. Starter und Anwendungsverhalten
Wenn Einsteiger hören „Starter eingebunden — und alles hat funktioniert", scheint da irgendwelche Mystik im Spiel zu sein. Dabei ist die Logik einfach: Der Starter bringt den nötigen Bibliothekssatz auf den Classpath, und Spring Boot kann dann über einem solchen Classpath eine sinnvolle Infrastruktur aufbauen. Das heißt, der Starter „schaltet ein Feature ein" nicht per Zauberknopf, sondern ändert den verfügbaren Stack der Anwendung.
Zum Beispiel ist spring-boot-starter-webmvc nicht „ein Jar mit Web-Code", sondern ein gewähltes Web-Szenario auf Servlet-Stack. Daher kommen mit ihm Spring MVC-Module, JSON-Unterstützung, ein eingebetteter Server und andere Teile mit, ohne die ein solcher Service normalerweise nicht funktioniert.
Fürs Erste reicht es, einen Gedanken zu behalten: Ein Starter beeinflusst das Verhalten über den Classpath. Wie man diesen Graphen wirklich liest und woher Tomcat, Jackson und andere Bibliotheken tatsächlich kommen, findet man über den Dependency Tree heraus.
5. Direkte Abhängigkeiten in catalog-service
catalog-service ist in diesem Kurs ein Lehrprojekt, soll aber einem normalen, lebendigen Service ähneln. Und eine der wichtigsten Gewohnheiten, die es früh zu entwickeln gilt: build.gradle.kts nicht zu einem Lager zufälliger Jars zu machen. Die Liste direkter Abhängigkeiten sollte kurz sein, denn jede Zeile ist eine Entscheidung, die den Classpath und das Verhalten der Anwendung verändert.
Wenn man kurz vom aktuellen catalog-service abstrahiert und ein minimales Boot-Beispiel nimmt, kann die Menge direkter Abhängigkeiten so aussehen. Das ist genau ein Minimalbeispiel, kein aktueller Tages-Snapshot:
dependencies {
// Grundgerüst Spring Boot (ohne Bindung an Web/DB usw.)
implementation("org.springframework.boot:spring-boot-starter")
// Test-„Paket": JUnit + Spring Test + Utilities
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
Dieses Beispiel ist genau als Abstraktion nützlich: Man sieht, dass es wenige direkte Abhängigkeiten gibt und jede Zeile eine Rolle ausdrückt, keine Classpath-Details.
Der Working-Snapshot des catalog-service, von dem wir jetzt ausgehen, ist konkreter: Der Service wird über ein Web-Szenario aufgebaut, nicht über einen Basis-Starter. Sein Kern ist spring-boot-starter-webmvc plus spring-boot-starter-test. Wenn später auf diesem Snapshot eine Diagnose-Schicht nötig wird, wächst der Build buchstäblich um eine verständliche Zeile:
dependencies {
// Web MVC auf Servlet-Stack: Controller, JSON, eingebetteter Server und grundlegende Web-Infrastruktur
implementation("org.springframework.boot:spring-boot-starter-webmvc")
// Diagnose: health/info/metrics und andere Actuator-Möglichkeiten
implementation("org.springframework.boot:spring-boot-starter-actuator")
// Test-Starter — bleibt meist einer, ohne manuellen Test-Stack-Aufbau
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
Das ist bereits eine Erweiterung der aktuellen Baseline, kein verdeckter Ersatz. Auch mit Actuator liest sich der Build weiterhin als Liste von Fähigkeiten: Web, Diagnose, Tests. Wir listen Tomcat, Jackson und Logback nicht manuell auf — das erledigt der gewählte Starter-Satz.
Vergleiche das mit dem „manuellen" Stil, den Einsteiger nach ein paar Google-Suchanfragen oft anwenden. Er wirkt „ernsthaft", ist aber in der Praxis fast immer schlechter:
dependencies {
// „Manuelle" Modulauswahl für die Web-Schicht: wirkt kontrolliert, aber Versionskonflikt ist leicht möglich
implementation("org.springframework:spring-webmvc")
implementation("org.apache.tomcat.embed:tomcat-embed-core")
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("ch.qos.logback:logback-classic")
implementation("org.slf4j:slf4j-api")
}
Das Problem liegt nicht darin, dass diese Bibliotheken „schlecht" sind. Das Problem ist ein anderes: Du hast die Rolle des Spring Boot-Teams übernommen. Jetzt bist du für Kompatibilität, den richtigen Modulsatz, Versionsabstimmung und vollständige Vollständigkeit verantwortlich. Für ein Lehrprojekt ist das garantierte kognitive Überlastung. Für Produktion ist dieser Weg manchmal gerechtfertigt — aber nur, wenn du bereits gut verstehst, was du tust und warum.
Im Kursrahmen wollen wir, dass build.gradle.kts wie eine Liste des Projektbedarfs aussieht — Web, Actuator, Tests — und nicht wie eine Aufzählung aller inneren Bauteile.
6. Starter und gewöhnliche Abhängigkeiten
Es ist leicht, ins Extrem zu verfallen: „Da es in Boot Starter gibt, wird alles über Starter eingebunden." Das stimmt nicht. Ein Starter ist ein gutes Format für Szenarien. Aber manchmal braucht man kein Szenario, sondern ein kleines Werkzeug. Zum Beispiel ist ein Annotation-Prozessor zur Generierung von Konfigurations-Metadaten kein Runtime-Feature, sondern Build-Zeit-Unterstützung. Und dafür braucht man keinen Starter.
Im catalog-service gibt es eine Abhängigkeit, die oft mit einem Starter verwechselt wird, nur weil sie auch von Boot stammt. Aber ihre Rolle ist eine andere: spring-boot-configuration-processor. Sie hilft IDE und Build, deine benutzerdefinierten @ConfigurationProperties zu verstehen, und wird als Annotation-Prozessor eingebunden.
In Gradle sieht das normalerweise so aus:
dependencies {
// Nur zur Kompilierungszeit nötig: generiert Metadaten für IDE/Hinweise zu @ConfigurationProperties
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
// Test-Stack — separat, um den Runtime-Classpath nicht zu belasten
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
Und hier taucht ein weiterer wichtiger Gedanke für Einsteiger auf: Nicht jede Abhängigkeit muss in implementation liegen. Manchmal ist es testImplementation — nur für Tests nötig, manchmal annotationProcessor — zur Kompilierungszeit nötig, manchmal developmentOnly — für lokale Entwicklung nötig. Wenn alles wahllos in implementation landet, wird der Classpath schwerer, lauter und weniger vorhersehbar.
Starter helfen, den Kern-Stack in Ordnung zu halten, heben aber den gesunden Menschenverstand nicht auf. Jetzt lautet die Grundfrage einfach anders: „Brauche ich ein Szenario — Starter, oder ein Werkzeug — gewöhnliche Bibliothek/Prozessor?"
7. Typische Fehler beim Umgang mit Startern
Fehler mit Startern passieren meist nicht, weil sie schwierig sind, sondern weil sie das vertraute Modell „eine Zeile = eine Bibliothek" aufbrechen. Anfangs ist das wie von einem Fahrrad auf einen E-Scooter umsteigen: scheinbar dasselbe, aber plötzlich schneller — und in eine Wand zu fahren ist auch einfacher. Hier sind einige Fehler, die man frühzeitig erkennen sollte, um Dependency Management nicht zur Lotterie zu machen.
Fehler Nr. 1: Denken, ein Starter sei „ein fettes Jar mit allem drin".
Ein Starter ist weder ein „Fat Jar" noch eine monolithische Bibliothek. Er entfaltet sich in einen Abhängigkeitssatz, und dieser Satz bildet den Classpath. Wenn du dir das Modell „da drin steckt Magie" merkst, wirst du unweigerlich aufhören zu verstehen, was wirklich eingebunden ist und warum die Anwendung sich genau so verhält.
Fehler Nr. 2: Einen Starter einbinden und daneben manuell Low-Level-Module desselben Szenarios hinzufügen.
Zum Beispiel spring-boot-starter-webmvc einbinden und daneben noch spring-webmvc, tomcat-embed-core und ein paar Jackson-Module „zur Sicherheit" hinzufügen. Das wirkt wie Fürsorge, ist aber in Wirklichkeit Duplizierung und potenzielle Konflikte. Wenn du bereits einen Starter hast, geh zunächst davon aus, dass er den Basis-Satz mitgebracht hat, und füge erst dann hinzu, was wirklich fehlt — und was du erklären kannst.
Fehler Nr. 3: Einen Starter nach „ähnlichem Namen" wählen, nicht nach Aufgabe.
Im Boot-Ökosystem gibt es Abhängigkeiten, die fast gleich klingen. Einsteiger wählen oft nach dem Prinzip „der Name enthält web — also ist das richtig." Besser ist es, zunächst das Szenario in Worten zu formulieren: „Ich will Servlet MVC" oder „Ich will Diagnose" — und dann passend den Starter zu wählen. Der Name ist ein Hinweis, aber keine Garantie für den richtigen Treffer.
Fehler Nr. 4: Erwarten, dass der Starter die Architektur der Anwendung löst.
Der Starter löst das Problem der Abhängigkeitsschicht: Er bringt einen abgestimmten Bibliothekssatz. Er löst nicht, wie Pakete organisiert werden, wo die Business-Logik liegt, wie Services heißen und welche Endpoints gemacht werden. Wenn du erwartest, dass „Starter eingebunden — und alles wurde richtig", fängst du sehr schnell an, deine Anwendung nach dem Prinzip „Na, Boot ist doch schlau" zu bauen.
Fehler Nr. 5: Den Build zu „optimieren" und „überflüssige" transitive Abhängigkeiten zu entfernen, ohne ihre Rolle zu verstehen.
Wenn du den Abhängigkeitsgraphen zum ersten Mal siehst, wirkt er riesig. Das ist eine normale Reaktion. Aber der Wunsch, den Graphen zu „bereinigen" ohne Verständnis führt schnell dazu, dass die Anwendung nicht mehr startet — die Ursache: eine versehentlich entfernte Bibliothek. Starter existieren genau deshalb, weil man in einem komplexen Stack normalerweise nicht einfach „Überflüssiges entfernen" kann, ohne Konsequenzen.
GO TO FULL VERSION