Spring Boot: Einstieg für Java-Entwickler

Spring Boot: Einstieg für Java-Entwickler

Spring Boot: Einstieg für Java-Entwickler

Artikel-Übersicht

Spring Boot bringt eine Anwendung in wenigen Minuten zum Laufen — und genau darin liegt die Schwierigkeit: Vieles passiert, ohne dass man es angeordnet hat. Dieser Einstieg setzt Java voraus und zielt darauf, dass du danach nicht nur weißt, wie du es benutzt, sondern was im Hintergrund geschieht. Er beginnt mit der Abgrenzung zwischen Spring und Spring Boot, erklärt die Abhängigkeitsumkehr als das Prinzip, ohne das nichts verständlich ist, und führt dann durch das erste Projekt. Der zentrale Abschnitt behandelt die automatische Konfiguration und wie man sie sichtbar macht. Dazu kommen eine Schnittstelle mit Fehlerbehandlung, Persistenz samt dem N+1-Problem, Konfiguration und Profile, Testen mit der häufigsten Falle — und die ehrliche Grenze, ab der Spring Boot zu viel ist.

Was Spring Boot ist

Spring Boot ist eine Vorkonfiguration des Spring-Frameworks, die Anwendungen ohne XML-Konfiguration und mit eingebettetem Webserver startfähig macht.

Die Abgrenzung in drei Sätzen: Spring ist ein umfangreiches Framework mit Modulen für Abhängigkeitsverwaltung, Datenzugriff, Web, Sicherheit und vieles mehr. Es zu benutzen bedeutete früher, umfangreiche Konfiguration zu schreiben und einen Anwendungsserver bereitzustellen. Spring Boot legt sich darüber und trifft sinnvolle Vorgaben: Es erkennt anhand der eingebundenen Abhängigkeiten, was du vorhast, konfiguriert es entsprechend und packt einen Webserver mit ins Ergebnis.

Der Grundsatz dahinter heißt „Konvention vor Konfiguration": Wenn du dich an die üblichen Muster hältst, musst du nichts einstellen. Weichst du ab, kannst du jede Vorgabe überschreiben.

Der Preis: Man bekommt ein System, in dem viel passiert, das man nicht angeordnet hat. Solange es funktioniert, ist das bequem. Sobald es nicht funktioniert, muss man verstehen, was da eigentlich passiert — dafür ist der Abschnitt zur automatischen Konfiguration da.

Abhängigkeitsumkehr in einem Absatz

Ohne dieses Prinzip ist Spring nicht verständlich.

Objekte erzeugen ihre Abhängigkeiten nicht selbst, sondern bekommen sie hereingereicht.

// Nicht so: die Klasse entscheidet, was sie benutzt
class RechnungService {
    private final RechnungRepository repo = new JpaRechnungRepository();
}

// Sondern so: sie sagt, was sie braucht
@Service
class RechnungService {
    private final RechnungRepository repo;

    RechnungService(RechnungRepository repo) {   // Spring reicht es herein
        this.repo = repo;
    }
}

Spring erzeugt beim Start alle mit @Component, @Service, @Repository oder @Configuration markierten Objekte, erkennt an den Konstruktorparametern, was sie brauchen, und setzt sie zusammen.

Der praktische Gewinn ist die Testbarkeit: Im Test reicht man eine Attrappe herein, und die Klasse ist ohne Datenbank prüfbar.

Und der Architekturbezug, der über Spring hinausgeht: Das ist genau das Prinzip, auf dem Clean Architecture aufbaut. Wenn RechnungRepository ein Interface ist, das die Fachlogik definiert, und die Datenbankschicht es implementiert, zeigt die Abhängigkeit nach innen. Spring liefert dafür den Mechanismus — die Entscheidung, ihn so zu nutzen, triffst du.

Empfehlung: Konstruktorinjektion, nicht Feldinjektion. Sie macht Abhängigkeiten sichtbar, erlaubt final und funktioniert ohne Spring im Test.

Das erste Projekt

Über den Spring Initializr wählt man Java-Version, Bauwerkzeug und Startpakete. Für eine einfache Schnittstelle mit Datenbank: Spring Web, Spring Data JPA, Validation und ein Datenbanktreiber.

Die Struktur:

src/main/java/de/beispiel/rechnungen/
├── RechnungenApplication.java     ← Einstiegspunkt
├── web/RechnungController.java    ← HTTP
├── domain/Rechnung.java           ← Fachlichkeit
└── persistence/RechnungRepository.java

src/main/resources/
└── application.yaml               ← Konfiguration

Der Einstiegspunkt ist kurz:

@SpringBootApplication
public class RechnungenApplication {
    public static void main(String[] args) {
        SpringApplication.run(RechnungenApplication.class, args);
    }
}

@SpringBootApplication fasst drei Dinge zusammen: die Kennzeichnung als Konfigurationsklasse, das Einschalten der automatischen Konfiguration und die Suche nach Komponenten ab dem Paket dieser Klasse abwärts.

Der letzte Punkt ist eine häufige Fehlerquelle: Komponenten in einem Paket oberhalb oder neben dem Einstiegspunkt werden nicht gefunden. Wenn eine Klasse trotz Annotation nicht erzeugt wird, ist fast immer die Paketstruktur der Grund.

Was die automatische Konfiguration tut

Der Abschnitt, der den Artikel trägt.

Beim Start durchsucht Spring Boot den Klassenpfad und wertet eine Liste von Konfigurationsklassen aus. Jede davon trägt Bedingungen:

@ConditionalOnClass(DataSource.class)          // nur wenn die Klasse da ist
@ConditionalOnMissingBean(DataSource.class)    // nur wenn du keine eigene gebaut hast
@ConditionalOnProperty("spring.datasource.url")

Daraus ergibt sich eine Kette von Wenn-Dann-Entscheidungen: Ist ein Datenbanktreiber im Klassenpfad und hat der Entwickler keine eigene Datenquelle definiert und ist eine URL konfiguriert — dann erzeuge eine Datenquelle mit diesen Vorgaben.

Das ist kein Zauber, sondern eine einsehbare Bedingungskette. Und man kann sie sich anzeigen lassen:

java -jar anwendung.jar --debug

Die Ausgabe enthält einen Bericht mit drei Abschnitten: welche automatischen Konfigurationen angewendet wurden, welche nicht und aus welchem Grund. Der zweite Teil ist bei der Fehlersuche der wertvollere — dort steht wörtlich, welche Bedingung nicht erfüllt war.

Eine Vorgabe überschreiben geht auf zwei Wegen: über eine Eigenschaft in der Konfiguration, oder indem man selbst ein passendes Objekt bereitstellt. Wegen @ConditionalOnMissingBean tritt die Vorgabe dann zurück.

@Bean
ObjectMapper objectMapper() {          // ersetzt die Vorgabe vollständig
    return new ObjectMapper().findAndRegisterModules();
}

Der Satz, auf den es hinausläuft: Die automatische Konfiguration ist eine Kette von Bedingungen, und sie ist einsehbar. Wer den Bericht einmal gelesen hat, sucht Konfigurationsprobleme nicht mehr im Nebel.

Eine Schnittstelle bauen

@RestController
@RequestMapping("/rechnungen")
class RechnungController {

    private final RechnungService service;

    RechnungController(RechnungService service) { this.service = service; }

    @GetMapping("/{nummer}")
    RechnungDto lesen(@PathVariable String nummer) {
        return service.finde(nummer)
            .map(RechnungDto::von)
            .orElseThrow(() -> new RechnungNichtGefunden(nummer));
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    RechnungDto anlegen(@Valid @RequestBody NeueRechnung eingabe) {
        return RechnungDto.von(service.anlegen(eingabe));
    }
}

Zur Fehlerbehandlung gehört eine zentrale Stelle, damit das Format über die ganze Schnittstelle gleich bleibt:

@RestControllerAdvice
class Fehlerbehandlung {

    @ExceptionHandler(RechnungNichtGefunden.class)
    ProblemDetail nichtGefunden(RechnungNichtGefunden e) {
        ProblemDetail p = ProblemDetail.forStatus(HttpStatus.NOT_FOUND);
        p.setTitle("Rechnung nicht gefunden");
        p.setProperty("fehlercode", "RECHNUNG_NICHT_GEFUNDEN");
        return p;
    }
}

ProblemDetail ist die eingebaute Umsetzung des standardisierten Fehlerformats. Warum ein einheitliches Fehlerformat mit stabilem Fehlercode wichtiger ist als schöne Meldungstexte, steht in API-Design: Schnittstellen entwerfen, die man nicht bereut.

Wer den umgekehrten Weg gehen will — erst die Spezifikation, dann das Servergerüst daraus erzeugen —, findet ihn in OpenAPI Generator.

Persistenz

Spring Data nimmt einem die Implementierung ab: Man schreibt ein Interface, die Umsetzung entsteht zur Laufzeit.

interface RechnungRepository extends JpaRepository<RechnungEntity, Long> {
    Optional<RechnungEntity> findByNummer(String nummer);
    List<RechnungEntity> findByStatusAndErstelltAfter(Status status, Instant ab);
}

Aus den Methodennamen wird die Abfrage abgeleitet. Das ist bequem — und der Punkt, an dem man aufpassen muss.

Die Abstraktion verbirgt SQL, das trotzdem ausgeführt wird. Sichtbar machen:

spring:
  jpa:
    properties:
      hibernate:
        format_sql: true
logging:
  level:
    org.hibernate.SQL: DEBUG

Das N+1-Problem ist der häufigste Fallstrick. Man lädt hundert Rechnungen, greift in einer Schleife auf deren Positionen zu — und statt einer Abfrage laufen hunderteins. Im Test mit fünf Datensätzen fällt das nicht auf, in Produktion schon.

Die Gegenmittel: eine Abfrage mit ausdrücklichem JOIN FETCH, ein Entitätsgraph, oder ein Stapelabruf über die Konfiguration. Erkennen kann man es nur, wenn die Abfragen im Protokoll sichtbar sind — deshalb steht die Protokollierung oben.

Zur Trennung von Fachmodell und Datenbankentität: Ob RechnungEntity und Rechnung zwei Klassen sind oder eine, ist eine Abwägung. Zwei Klassen bedeuten sauberere Trennung und doppelte Pflege. Wann sich das lohnt und wann nicht, behandle ich in Clean Architecture — die kurze Fassung: erst, wenn die Modelle tatsächlich auseinanderlaufen.

Konfiguration und Profile

spring:
  application:
    name: rechnungen
  datasource:
    url: ${DB_URL}
    username: ${DB_USER}
    password: ${DB_PASSWORD}

---
spring:
  config:
    activate:
      on-profile: lokal
  datasource:
    url: jdbc:postgresql://localhost:5432/rechnungen

Zwei Regeln:

Geheimnisse kommen aus der Umgebung, nicht aus der Datei. Die ${...}-Schreibweise liest Umgebungsvariablen — und was in der Umgebung steht, landet nicht im Repository.

Profile für Umgebungsunterschiede, nicht für fachliche Varianten. Ein Profil lokal mit anderer Datenbank ist richtig; ein Profil grosskunde mit anderer Geschäftslogik ist ein Fehler, der sich rächt.

Für typisierte Konfiguration lohnt @ConfigurationProperties — dann sind Einstellungen ein Objekt mit Feldern statt verstreuter Zeichenketten.

Testen

Spring Boot bringt umfangreiche Teststützen mit, und daraus entsteht die häufigste Falle:

@SpringBootTest        // fährt den GESAMTEN Anwendungskontext hoch
class RechnungServiceTest { ... }

Wer das für jeden Test macht, hat eine Testsuite, die Minuten braucht. Und eine langsame Testsuite wird nicht mehr laufen gelassen.

Die Abstufung, von schnell nach langsam:

Art Was hochfährt Wofür
Reiner Einheitentest nichts Fachlogik — der Normalfall
@WebMvcTest nur die Web-Schicht Controller, Serialisierung, Validierung
@DataJpaTest nur die Persistenzschicht Abfragen, Zuordnungen
@SpringBootTest alles wenige Integrationstests

Der wichtigste Punkt: Fachlogik braucht Spring nicht. Eine Klasse mit Konstruktorinjektion lässt sich mit new erzeugen und mit Attrappen versorgen. Wenn das nicht geht, ist die Klasse zu eng mit dem Framework verwoben — und das ist ein Entwurfsproblem, kein Testproblem.

Warum eine langsame Testsuite mehr kostet als sie einbringt, behandle ich in Technische Schulden und Refactoring.

Wann Spring Boot zu viel ist

Ehrlich, weil es diese Fälle gibt:

  • Sehr kleine Dienste. Für einen Dienst mit zwei Endpunkten ist der Umfang unverhältnismäßig.
  • Kurze Startzeiten gefordert. Eine Spring-Boot-Anwendung braucht typischerweise einige Sekunden bis zur Betriebsbereitschaft. Für Serverless-Funktionen, die häufig kalt starten, ist das ungeeignet — warum, steht in Cloud-Architektur und KI.
  • Geringer Speicherbedarf gefordert. Der Grundverbrauch liegt spürbar über schlankeren Alternativen.

Für die ersten beiden Fälle gibt es zwei Auswege: schlankere Java-Rahmenwerke, die auf schnellen Start ausgelegt sind, oder das Übersetzen in ein natives Abbild — das verkürzt die Startzeit erheblich, verlängert aber den Bauvorgang und schränkt dynamische Verhaltensweisen ein.

Und der Gegenfall: Für langlebige Unternehmensanwendungen mit Datenbank, Sicherheit, Nachrichtenverarbeitung und Betriebsanbindung ist Spring Boot schwer zu schlagen — nicht wegen einzelner Funktionen, sondern weil alles zusammenpasst und die Fragen, die auftreten, schon jemand beantwortet hat.

Häufige Fragen

Was ist Spring Boot? Eine Vorkonfiguration des Spring-Frameworks: Es erkennt anhand der Abhängigkeiten, was du vorhast, konfiguriert es mit sinnvollen Vorgaben und bringt einen eingebetteten Webserver mit.

Was ist der Unterschied zwischen Spring und Spring Boot? Spring ist das Framework, Spring Boot die Schicht darüber, die es ohne umfangreiche Konfiguration startfähig macht.

Wie startet man ein Projekt? Über den Spring Initializr Java-Version, Bauwerkzeug und Startpakete wählen, das Ergebnis herunterladen und die Anwendungsklasse starten.

Was macht die automatische Konfiguration? Sie wertet beim Start eine Kette von Bedingungen aus — welche Klassen im Klassenpfad liegen, welche Objekte du selbst bereitstellst, welche Eigenschaften gesetzt sind — und erzeugt daraus die passenden Objekte. Mit --debug sieht man den vollständigen Bericht.

Wie bindet man eine Datenbank an? Über Spring Data JPA: ein Interface schreiben, das von JpaRepository erbt. Die Umsetzung entsteht zur Laufzeit aus den Methodennamen.

Wann ist Spring Boot zu viel? Bei sehr kleinen Diensten, bei Anforderungen an kurze Startzeit und bei knappem Speicher. Für langlebige Unternehmensanwendungen ist es schwer zu schlagen.

Zusammenfassung und praktische Tipps

Spring Boot nimmt Entscheidungen ab — und wer weiß, welche, behält die Kontrolle. Die wichtigsten Punkte:

  • Spring Boot ist die Vorkonfiguration, Spring das Framework.
  • Konstruktorinjektion statt Feldinjektion. Sichtbare Abhängigkeiten, final möglich, testbar ohne Spring.
  • Komponenten müssen unterhalb der Anwendungsklasse liegen, sonst werden sie nicht gefunden.
  • --debug zeigt den Bericht der automatischen Konfiguration — inklusive der Gründe, warum etwas nicht angewendet wurde.
  • SQL-Protokollierung einschalten. Ohne sie sieht man das N+1-Problem erst in Produktion.
  • Nicht jeder Test braucht den ganzen Kontext. Fachlogik testet man ohne Spring.
  • Geheimnisse aus der Umgebung, Profile nur für Umgebungsunterschiede.

Mein Rat für den Einstieg: Starte deine erste Anwendung einmal mit --debug und lies den Bericht der automatischen Konfiguration von oben bis unten. Das dauert zwanzig Minuten und ist die beste investierte Zeit im ganzen Einstieg — danach ist Spring Boot kein System mehr, das Dinge tut, sondern eines, dessen Entscheidungen du nachvollziehen kannst.

Ressourcen