Zum Hauptinhalt springen

AB 01 – Das Projekt aufsetzen und den ersten Endpunkt bauen

Die Situation

Die Personalabteilung der HiTec GmbH verwaltet ihre Mitarbeiterdaten bislang in einer Tabellenkalkulation. Künftig sollen mehrere Programme darauf zugreifen: die neue Weboberfläche, eine App für das Außendienstteam und die Zeiterfassung an der Pforte.

Dein Auftrag: Baue ein Backend, das die Personendaten verwaltet und sie allen drei Programmen über eine einheitliche Schnittstelle zur Verfügung stellt.

In diesem ersten Arbeitsblatt entsteht noch keine Datenverwaltung. Es geht darum, das Projekt zum Laufen zu bringen und den ersten Endpunkt zu bauen — damit du siehst, dass die Grundlage steht.

Lernziele

Nach Bearbeitung dieses Arbeitsblatts kannst du:

  • ein Spring-Boot-Projekt mit dem Spring Initializr erzeugen und die benötigten Abhängigkeiten auswählen
  • den Aufbau eines Spring-Boot-Projekts erklären (pom.xml, application.properties, Application-Klasse)
  • einen REST-Controller mit einem GET-Endpunkt implementieren
  • einen Record als Datenklasse einsetzen und erklären, warum er sich für Antwortobjekte eignet
  • den Zustand deiner laufenden Anwendung über den Actuator prüfen
Vorwissen

Lies vorher die Infoblätter Was ist ein Webservice? und Das REST-Paradigma. Für den Aufbau der Nachrichten hilft HTTP kompakt.

Warum ein Spring-Boot-Projekt kein normales Java-Projekt ist

Ein Webservice muss über das Netzwerk erreichbar sein. Dafür braucht es einen Webserver. Früher hat man den getrennt installiert und die eigene Anwendung dort hineingelegt.

Spring Boot dreht das um: Der Webserver wird Teil der Anwendung. Am Ende entsteht eine einzige .jar-Datei, die alles enthält, was sie zum Laufen braucht — man startet sie wie ein gewöhnliches Java-Programm.

Klassisch1Webserver installieren2Anwendung hineinlegen3Server startenSpring Boot1java -jar app.jarfertigder Server ist eingebaut

Das ist der Grund, warum solche Anwendungen sich gut in Container packen und in der Cloud betreiben lassen. Und es ist der Grund, warum ein Spring-Boot-Projekt nicht von Hand angelegt, sondern über den Spring Initializr erzeugt wird.

Aufgaben

Aufgabe 1 – Projekt in IntelliJ erzeugen

IntelliJ bringt den Spring Initializr eingebaut mit. Du musst also nichts herunterladen und nichts entpacken — die Entwicklungsumgebung erledigt das.

Schritt 1: Den Assistenten öffnen

Schritt 2: Den Generator „Spring Boot" wählen

Der Assistent sieht diesmal anders aus als gewohnt: Links stehen nicht nur die üblichen Projektarten, sondern darunter ein zweiter Abschnitt Generators.

Nicht verwechseln

Ganz oben in derselben Liste steht unter New Project auch ein Eintrag Java. Der erzeugt ein gewöhnliches Java-Projekt ohne Spring Boot — damit läuft später nichts von dem, was in diesen Arbeitsblättern steht.

Du brauchst den Eintrag weiter unten im Abschnitt Generators.

Schritt 3: Die Projektangaben eintragen

Der Assistent mit ausgewähltem Generator Spring Boot und den Eingabefeldern

Zur Abbildung

Sie zeigt den Dialog vor dem Ausfüllen — die Felder stehen noch auf den Voreinstellungen. Auch der Ordner unter Location ist nur ein Beispiel; nimm deinen eigenen.

Maßgeblich sind die Werte aus der folgenden Tabelle.

FeldWertHinweis
NamepersonenverwaltungDer Projektname
Locationz. B. C:\projekteDer Ordner, in dem das Projekt angelegt wird
LanguageJavaist meist schon gewählt
TypeMaven⚠️ Vorgabe ist Gradle - Groovy — unbedingt umstellen
Groupde.szut
Artifactpersonenverwaltung
Package namede.szut.personenverwaltungfüllt sich meist automatisch
JDKVersion 26
Java26⚠️ Vorgabe ist oft 17
PackagingJar
Drei Stolperfallen

Type: IntelliJ schlägt Gradle - Groovy vor. Wir arbeiten mit Maven — sonst passt später nichts zusammen, was in den Arbeitsblättern steht.

Java-Version: Die Vorgabe ist häufig 17. Stelle beides auf 26 — sowohl das JDK als auch das Feld Java.

Pfade mit Umlauten: Ein Ort wie C:\Schüler\Übungen\ führt bei manchen Werkzeugen zu schwer auffindbaren Fehlern. Nimm etwas wie C:\projekte.

Schritt 4: Die Abhängigkeiten auswählen

Auf der zweiten Seite des Assistenten wählst du aus, welche Bausteine dein Projekt bekommen soll.

Die Auswahl der Abhängigkeiten

Die Seite ist dreigeteilt: Oben die Spring-Boot-Version, in der Mitte die nach Kategorien geordneten Abhängigkeiten, rechts oben die Beschreibung des gerade markierten Eintrags und rechts unten die Liste Added dependencies mit dem, was du bereits gewählt hast.

Was SNAPSHOT bedeutet

Versionen mit diesem Zusatz sind unfertige Entwicklungsstände, die sich täglich ändern können. Nimm immer eine Version ohne diesen Zusatz.

Die Abbildungen zeigen eine ältere Nummer

Spring Boot erscheint mehrmals im Jahr neu. In den Screenshots steht deshalb möglicherweise eine kleinere Versionsnummer als die, die dir angeboten wird. Nimm die neueste 4.1.x aus der Liste — die Arbeitsblätter passen auf jede davon.

AbhängigkeitKategorieWofür
Spring WebWebDamit die Anwendung HTTP versteht und REST-Endpunkte anbieten kann
Spring Data JPASQLFür den Datenbankzugriff, ohne SQL selbst zu schreiben
H2 DatabaseSQLEine Datenbank, die keine Installation braucht
Spring Boot ActuatorOpsLiefert Betriebsinformationen über die laufende Anwendung

Die Kategorien lassen sich über den Pfeil links aufklappen. Schneller geht es über das Suchfeld:

Das Suchfeld filtert die Abhängigkeiten

Auf den genauen Namen achten

Bei der Suche nach Spring Web erscheinen drei ähnliche Einträge:

  • Spring Web ← dieser ist gemeint
  • Spring Reactive Web — eine andere Programmierweise, passt nicht zu diesem Tutorial
  • Spring Web Services — für SOAP, nicht für REST

Verwechselst du sie, kompiliert später nichts von dem, was in den Arbeitsblättern steht.

Kontrolle vor dem Weitermachen

Rechts unter Added dependencies müssen am Ende genau vier Einträge stehen. Steht dort noch No dependencies added, hat das Anhaken nicht funktioniert — klicke direkt auf das kleine Kästchen vor dem Namen.

Der erste Start dauert

Maven lädt jetzt über hundert Dateien aus dem Internet. Beim ersten Mal dauert das mehrere Minuten, danach nur noch Sekunden. Warte ab, bis die Fortschrittsanzeige unten verschwunden ist.

Warum aus vier Einträgen über hundert Dateien werden, steht im Infoblatt Maven und Abhängigkeiten.


Alternative: über start.spring.io im Browser

Wenn du eine andere Entwicklungsumgebung benutzt (VS Code, Eclipse) oder das Projekt lieber im Browser erzeugst

Der Spring Initializr ist ein Webdienst. IntelliJ benutzt im Hintergrund genau diesen Dienst — du kannst ihn aber auch direkt aufrufen. Das Ergebnis ist dasselbe.

1. Öffne start.spring.io im Browser.

2. Stelle die folgenden Werte ein:

FeldWert
ProjectMaven
LanguageJava
Spring Bootdie neueste 4.1.x (ohne den Zusatz SNAPSHOT)
Groupde.szut
Artifactpersonenverwaltung
Namepersonenverwaltung
Package namede.szut.personenverwaltung
PackagingJar
Java26

3. Füge über ADD DEPENDENCIES dieselben vier Abhängigkeiten hinzu: Spring Web, Spring Data JPA, H2 Database, Spring Boot Actuator.

Spring Initializr mit den einzustellenden Werten

4. Klicke auf GENERATE. Entpacke die heruntergeladene ZIP-Datei an einen Ort ohne Leerzeichen und Umlaute im Pfad.

5. Öffne den entpackten Ordner anschließend in deiner Entwicklungsumgebung.

Aufgabe 2 – Projekt öffnen und starten

Hast du den Weg über IntelliJ genommen, ist das Projekt bereits offen — dann übersprings die nächste Aufgabe und lies gleich weiter.

Sieh dir an, was der Initializr erzeugt hat:

personenverwaltung/
├── pom.xml ← Projektbeschreibung: Abhängigkeiten, Java-Version
├── mvnw / mvnw.cmd ← der Maven Wrapper
└── src/
├── main/
│ ├── java/de/szut/personenverwaltung/
│ │ └── PersonenverwaltungApplication.java ← Startpunkt
│ └── resources/
│ └── application.properties ← Einstellungen
└── test/
└── java/… ← Tests
Der Maven Wrapper

mvnw (Linux/macOS) bzw. mvnw.cmd (Windows) lädt automatisch die Maven-Version, die zum Projekt gehört. Dadurch bauen alle im Team mit derselben Version — unabhängig davon, was auf dem einzelnen Rechner installiert ist.

In Betrieben gilt: Nutze den Wrapper, nicht dein lokales Maven.

Was Maven sonst noch tut, woher die Bibliotheken kommen und warum aus deinen vier ausgewählten Abhängigkeiten über hundert Dateien werden, steht im Infoblatt Maven und Abhängigkeiten.

In der Konsole erscheint zum Schluss eine Zeile wie:

Tomcat started on port 8080 (http) with context path '/'
Started PersonenverwaltungApplication in 2.417 seconds

Tomcat ist der eingebaute Webserver. Deine Anwendung ist jetzt unter http://localhost:8080 erreichbar.

Aufgabe 3 – Prüfen, ob die Anwendung wirklich läuft

Die Anwendung bietet noch keinen einzigen eigenen Endpunkt an. Der Actuator liefert aber bereits einen.

Du siehst etwas wie:

{"groups":["liveness","readiness"],"status":"UP"}

Entscheidend ist "status":"UP". Das Feld groups nennt zusätzliche Prüfgruppen, die der Actuator mitbringt — je nach Version kann die Antwort deshalb etwas anders aussehen.

Der Health-Endpunkt des Actuators

Beobachte den Unterschied

/actuator/health liefert 200 OK, /gibtesnicht liefert 404 Not Found. Der Browser zeigt beide Male irgendetwas an — der Unterschied steckt im Statuscode, nicht im sichtbaren Text.

Das ist der Grund, warum man bei Webservices immer auf den Statuscode schaut.

Aufgabe 4 – Der erste eigene Endpunkt

Jetzt baust du einen Endpunkt, der einen Begrüßungstext zurückgibt. Er braucht noch keine Datenbank.

Package oder Ordner — was denn nun?

Beides. Ein Package ist ein Ordner, mehr steckt physisch nicht dahinter: keine Konfigurationsdatei, kein Eintrag irgendwo. Zum Package wird der Ordner erst durch die erste Zeile der Dateien darin:

package de.szut.personenverwaltung.controller;

Ordner und diese Zeile müssen zueinander passen — das ist die ganze Regel.

Und wo liegt dieser Ordner? Nicht im Projektverzeichnis. Packages beginnen erst unterhalb von src/main/java:

personenverwaltung/ ← das Projektverzeichnis
├── pom.xml hier liegen KEINE Packages
├── mvnw
└── src/
├── main/
│ ├── java/
│ │ └── de/szut/personenverwaltung/ ← ab hier beginnen die Packages
│ │ ├── PersonenverwaltungApplication.java
│ │ └── controller/ ← dieses legst du gleich an
│ └── resources/
│ └── application.properties
└── test/

Die drei Ordner de, szut und personenverwaltung sind übrigens auch schon Packages — sie entsprechen den drei Teilen von de.szut.personenverwaltung. Spring Initializr hat sie beim Erzeugen des Projekts angelegt.

In IntelliJ gibt es zwei Wege dorthin:

WegWas du eingibst
Rechtsklick unterhalb von src/main/javaNewPackageden vollen Namen mit Punkten: de.szut.personenverwaltung.controller — IntelliJ legt daraus die verschachtelten Ordner an
Rechtsklick auf den Ordner personenverwaltungNewDirectorynur controller, ohne Punkte

Findest du den Eintrag Package nicht, hast du außerhalb des Quelltextordners geklickt. Schlimm ist das nicht — nimm Directory.

Die häufigste Panne: einen Namen mit Punkten in New → Directory eintippen. Dann entsteht ein Ordner, der wörtlich de.szut.personenverwaltung.controller heißt, und keine Klasse darin lässt sich übersetzen. Punkte gehören in New → Package, ein einzelner Name in beides.

Und wenn du dir das sparen willst: Du kannst den Ordner ganz weglassen und gleich die Klasse anlegen. Gib bei NewJava Class als Namen controller.Greeting ein — IntelliJ legt den Ordner mit an und schreibt die package-Zeile selbst.

src/main/java/de/szut/personenverwaltung/controller/Greeting.java
package de.szut.personenverwaltung.controller;

import java.time.LocalDateTime;

public record Greeting(String message, LocalDateTime time) {
}

Das ist die vollständige Klasse. Ein Record erzeugt automatisch Konstruktor, Zugriffsmethoden, equals(), hashCode() und toString().

Warum hier ein Record?

Ein Record ist unveränderlich: Einmal erzeugt, lässt sich sein Inhalt nicht mehr ändern.

Genau das will man bei einem Antwortobjekt. Es wird gebaut, verschickt — fertig. Niemand soll es unterwegs verändern. Die Sprache erzwingt hier, was die Architektur ohnehin verlangt.

Später wirst du sehen, dass Datenbankklassen aus genau dem umgekehrten Grund keine Records sein können.

src/main/java/de/szut/personenverwaltung/controller/WelcomeController.java
package de.szut.personenverwaltung.controller;

import java.time.LocalDateTime;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/welcome")
public class WelcomeController {

@GetMapping
public Greeting welcome() {
return new Greeting("Willkommen bei der Personenverwaltung!", LocalDateTime.now());
}
}

Was die drei Annotationen bedeuten:

AnnotationBedeutung
@RestControllerSagt Spring: Diese Klasse bietet REST-Endpunkte an. Rückgabewerte werden automatisch in JSON umgewandelt.
@RequestMapping("/api/v1/welcome")Der gemeinsame Pfadanfang aller Endpunkte dieser Klasse.
@GetMappingDiese Methode beantwortet GET-Anfragen an diesen Pfad.

Die Antwort des Welcome-Endpunkts im Browser

Beachte, was hier gerade passiert ist

Du hast ein Java-Objekt zurückgegeben — und im Browser steht JSON. Diese Umwandlung nennt man Serialisierung. Spring erledigt sie automatisch. Aus den Record-Komponenten message und time werden die JSON-Felder gleichen Namens.

Wie JSON aufgebaut ist und wie Java-Typen darauf abgebildet werden, steht im Infoblatt JSON.

Aufgabe 5 – Testfälle ausführen

Ab jetzt prüfst du deine Arbeit systematisch. Ein Testfall beschreibt, was du tust und was dabei herauskommen muss — unabhängig davon, wer ihn ausführt.

IDBeschreibungVorbedingungTestschritteErwartetes ErgebnisErgebnis
TF-01Anwendung startetProjekt ist geöffnet, Port 8080 ist frei
  1. main-Methode von PersonenverwaltungApplication ausführen
In der Konsole erscheint Started PersonenverwaltungApplication. Es wird keine Ausnahme geworfen.
TF-02Health-Endpunkt erreichbarAnwendung läuft
  1. Im Browser http://localhost:8080/actuator/health aufrufen
Status 200, die Antwort enthält "status":"UP"
TF-03Welcome-Endpunkt liefert JSONAnwendung läuft
  1. Im Browser http://localhost:8080/api/v1/welcome aufrufen
Status 200. Antwort enthält die Felder message und time.
TF-04Unbekannter Pfad wird abgewiesenAnwendung läuft
  1. http://localhost:8080/api/v1/welcomes aufrufen
  2. Statuscode in den Entwicklerwerkzeugen (F12 → Netzwerk) ablesen
Status 404 — nicht 200 und nicht 500.
TF-05Die Antwort ist als JSON gekennzeichnetAnwendung läuft
  1. http://localhost:8080/api/v1/welcome aufrufen
  2. F12 → Netzwerk → den Eintrag anklicken → Reiter Header
Unter den Antwort-Headern steht Content-Type: application/json.
Ein Fall fehlt hier noch — und das hat einen Grund

Was passiert bei richtiger Adresse, aber falscher Methode? Ein POST auf /api/v1/welcome wird mit 405 Method Not Allowed beantwortet — nicht mit 404, denn die Adresse gibt es ja. Nur die Methode passt nicht zu @GetMapping.

Ausprobieren kannst du das jetzt noch nicht: Über die Adresszeile des Browsers geht ausschließlich GET. Für alle anderen Methoden brauchst du ein Werkzeug — das bekommst du im nächsten Arbeitsblatt. Der zugehörige Testfall steht dann in Arbeitsblatt 03.

Warum Testfälle aufschreiben?

Solange du selbst entwickelst, hast du im Kopf, was funktionieren soll. Sobald jemand anderes prüft — oder du selbst in drei Wochen — ist dieses Wissen weg.

Ein guter Testfall nennt neben seiner Bezeichnung drei Dinge: die Vorbedingung, die Schritte und das erwartete Ergebnis. Er ist so genau, dass zwei Personen unabhängig voneinander zum selben Urteil kommen.

Zusammenfassung

Das hast du gelernt
  • Ein Spring-Boot-Projekt entsteht über den Spring Initializr und bringt seinen Webserver selbst mit.
  • Der Maven Wrapper (mvnw) sorgt dafür, dass alle mit derselben Maven-Version bauen.
  • @RestController und @GetMapping machen aus einer Java-Methode einen erreichbaren Endpunkt.
  • Ein Record ist eine unveränderliche Datenklasse — ideal für Antwortobjekte.
  • Spring wandelt Rückgabeobjekte automatisch in JSON um (Serialisierung).
  • Der Statuscode sagt mehr über den Erfolg einer Anfrage aus als der sichtbare Text.

Selbstkontrolle

Beantworte die Fragen, bevor du nachsiehst.

  1. Warum wird ein Spring-Boot-Projekt nicht als gewöhnliches Java-Projekt angelegt?
  2. Wozu dient die Annotation @RestController?
  3. Was ist der Unterschied zwischen @RequestMapping an der Klasse und @GetMapping an der Methode?
  4. Warum eignet sich ein Record gut für ein Antwortobjekt, aber schlecht für eine Klasse, deren Werte sich ändern sollen?
  5. Welchen Statuscode liefert ein Aufruf mit richtiger Adresse, aber falscher HTTP-Methode?
Antworten
  1. Weil die Anwendung ihren eigenen Webserver mitbringt und dafür eine besondere Projektstruktur und passende Abhängigkeiten braucht. Der Initializr stellt beides bereit.
  2. Die Annotation kennzeichnet die Klasse als REST-Controller. Spring findet sie beim Start automatisch und wandelt die Rückgabewerte ihrer Methoden in JSON um.
  3. @RequestMapping an der Klasse legt den gemeinsamen Pfadanfang für alle Endpunkte fest. @GetMapping an der Methode bestimmt, dass genau diese Methode GET-Anfragen beantwortet — und ergänzt gegebenenfalls den Pfad.
  4. Ein Record ist unveränderlich. Ein Antwortobjekt wird einmal erzeugt und verschickt, es soll sich nicht mehr ändern. Eine Klasse, deren Werte sich ändern sollen, braucht dagegen Setter — die ein Record nicht hat.
  5. 405 Method Not Allowed.