Das Projekt anlegen: der erste Endpunkt für das Mitfahrlabor-Backend

https://bildung.social/@oerinformatik/

https://oer-informatik.de/spring-boot-projekt-anlegen

Spring Boot, Spring Initializr, Gradle, Maven, REST-Controller, Record, JSON, Smoke-Test, HTTP-Client, Java

tl/dr; (ca. 17 min Lesezeit): Mit dem Spring Initializr entsteht das Projekt für das Mitfahrlabor-Backend. Du lernst die Projektstruktur, den Build mit Gradle oder Maven und die Startklasse kennen, schreibst den ersten REST-Controller für die Haltepunkte von Hand, rufst ihn mit dem Browser, einer .http-Datei oder der Shell auf und lässt den ersten Test laufen: einen Smoke-Test, der prüft, ob die Anwendung überhaupt startet [AI-Assisted]. (Zuletzt geändert am 11.11.2026)

Dieser Artikel ist Teil einer Artikelreihe über ein REST-Backend mit Spring Boot: Überblick und Fahrplan / 01 Anforderungen und API-Vertrag / 02 Projekt anlegen / Übungsaufgaben zu 02

Schon fit in diesem Thema? Nutze die Kann-Liste und die Leitfragen unter dem Artikel als Checkliste, ob Du die relevanten Kompetenzen bereits hast.

Dieser Artikel baut auf der Kann-Liste des Artikels Vom Auftrag zur API: Anforderungen und OpenAPI-Vertrag auf.

Als Spring-Boot-Grundlagen kann ich nach Durchlesen des Artikels…

  • … ein Spring-Boot-Projekt mit dem Spring Initializr anlegen und die nötigen Angaben (Build-Tool, Java-Version, Paket, Abhängigkeiten) begründen.

  • … die Ordner und Dateien eines Spring-Boot-Projekts benennen und ihre Aufgabe erklären.

  • … die Aufgabe der Annotation @SpringBootApplication und eines Starters erklären.

  • … einen REST-Controller mit @RestController, @RequestMapping, @GetMapping und @PathVariable notieren.

  • … mit einem Record eine Datenklasse für JSON-Antworten notieren.

  • … die Anwendung starten, einen Endpunkt per HTTP aufrufen und Statuscode und Body der Antwort deuten.

  • … einen Smoke-Test ausführen und erklären, was er prüft und was nicht.

Darüber hinaus kann ich als Spring-Boot-Expertenwissen…

  • … mit ResponseEntity und Optional einen Endpunkt notieren, der für unbekannte IDs 404 Not Found liefert.

  • … den Weg einer Anfrage vom Client über Tomcat, das DispatcherServlet und den Controller bis zur JSON-Antwort beschreiben.

Im ersten Artikel ist festgelegt worden, was das Backend können soll. Jetzt geht es an die Implementierung, ganz unten im V-Modell. Wir legen das Projekt an, schauen uns an, was Spring Boot uns mitgibt, und bauen einen ersten Endpunkt: GET /v1/haltepunkte. Noch ohne Datenbank und bewusst von Hand, damit Du siehst, was im nächsten Artikel der Codegenerator für Dich übernimmt.

Fahrplan der Artikelreihe im V-Modell, hervorgehoben ist die Implementierung, zu der Artikel 02 gehört (mit KI erstellt)

Für diesen Artikel brauchst Du ein JDK ab Version 25 und eine Entwicklungsumgebung mit Java-Unterstützung, wie im Artikel Grundlagen für Java-Framework-Projekte installieren beschrieben. Das fertige Projekt findest Du im Repository im Ordner src/02-projekt-anlegen.

Das Projekt mit dem Spring Initializr erzeugen

Ein Spring-Boot-Projekt schreibt niemand von Grund auf. Der Spring Initializr unter start.spring.io erzeugt ein lauffähiges Gerüst, das zu den gewählten Einstellungen passt. Die Entwicklungsumgebungen bieten denselben Dialog an: In VS Code mit dem Spring Boot Extension Pack über den Befehl „Spring Initializr: Create a Gradle Project“, in IntelliJ IDEA über „New Project“ und „Spring Boot“. Für das Mitfahrlabor wählen wir:

Einstellungen im Spring Initializr für das Mitfahrlabor-Backend
Einstellung Wert Begründung
Project Gradle - Kotlin Build-Tool der Reihe; Maven geht genauso (siehe Tabs unten)
Language Java
Spring Boot 4.1.2 aktuelle stabile Version (keine SNAPSHOT- oder RC-Version)
Group de.dbh umgekehrter Domainname der Schule
Artifact mitfahrlabor Name des Projekts und der späteren JAR-Datei
Package name de.dbh.mitfahrlabor Wurzelpaket, unter dem alle Klassen liegen
Packaging Jar ausführbare Datei mit eingebettetem Webserver
Java 25 aktuelle Version mit Langzeitunterstützung
Dependencies Spring Web für REST-Controller und den eingebetteten Webserver Tomcat

Mit diesem vorausgefüllten Link sind alle Felder schon gesetzt. Ein Klick auf „Generate“ lädt eine ZIP-Datei herunter. Entpacke sie und öffne den Ordner in Deiner Entwicklungsumgebung. Beim ersten Öffnen lädt der Build alle Bibliotheken aus dem Internet; das dauert ein paar Minuten.

Weitere Abhängigkeiten wie die Datenbank fügen wir erst hinzu, wenn wir sie brauchen. So siehst Du bei jedem Schritt, welche Zeile im Build was bewirkt.

Was im Projekt steckt

Der Initializr legt diese Struktur an:

mitfahrlabor/
├── build.gradle.kts            Build-Beschreibung (bei Maven: pom.xml)
├── settings.gradle.kts         Name des Projekts
├── gradlew, gradlew.bat        Gradle-Wrapper für Linux/macOS und Windows
├── gradle/wrapper/             Version und Starthilfe des Wrappers
└── src/
    ├── main/
    │   ├── java/de/dbh/mitfahrlabor/
    │   │   └── MitfahrlaborApplication.java    Startklasse
    │   └── resources/
    │       └── application.properties          Konfiguration
    └── test/
        └── java/de/dbh/mitfahrlabor/
            └── MitfahrlaborApplicationTests.java   erster Test

Die Trennung in src/main und src/test ist die Standard-Ordnerstruktur von Maven und Gradle: Produktivcode und Tests liegen getrennt, Testcode landet nie in der ausgelieferten Anwendung. Der Wrapper (gradlew bzw. mvnw) lädt beim ersten Aufruf genau die Version des Build-Tools herunter, die das Projekt braucht. Du musst Gradle oder Maven deshalb nicht selbst installieren. Mehr dazu steht in den Artikeln zu Gradle und Maven.

Spannend ist die Build-Beschreibung:

Datei build.gradle.kts:

plugins {
    java
    id("org.springframework.boot") version "4.1.2"
    id("io.spring.dependency-management") version "1.1.7"
}

group = "de.dbh"
version = "0.0.1-SNAPSHOT"

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(25)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-webmvc")
    testImplementation("org.springframework.boot:spring-boot-starter-webmvc-test")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.withType<Test> {
    useJUnitPlatform()
}

Drei Dinge sind typisch für Spring Boot:

  • Das Spring-Boot-Plugin bzw. der Parent legt fest, in welchen Versionen alle Bibliotheken zueinander passen. Deshalb steht bei den Abhängigkeiten keine Versionsnummer. Gradle braucht dafür zusätzlich das Plugin io.spring.dependency-management.

  • Starter sind Sammelpakete. spring-boot-starter-webmvc bringt alles für eine Web-Anwendung mit Spring MVC mit: den Webserver Tomcat, die JSON-Bibliothek Jackson und die Grundlagen von Spring. Das Gegenstück für Tests ist spring-boot-starter-webmvc-test mit JUnit, AssertJ, Mockito und den Testwerkzeugen von Spring. In älteren Spring-Boot-Versionen hieß der Starter spring-boot-starter-web.

  • Die Java-Version wird zentral festgelegt: bei Gradle über die Toolchain, für die Gradle auf dem Rechner ein passendes JDK sucht, bei Maven über die Eigenschaft java.version. Fehlt JDK 25, bricht der Build mit einer entsprechenden Meldung ab.

Die Startklasse und die Autokonfiguration

Die einzige Klasse, die der Initializr anlegt, ist kurz:

package de.dbh.mitfahrlabor;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MitfahrlaborApplication {

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

Die main-Methode übergibt den Start an SpringApplication.run(). Die eigentliche Arbeit steckt in der Annotation @SpringBootApplication. Annotationen sind Markierungen im Code, die selbst nichts tun, aber von einem Framework ausgewertet werden. @SpringBootApplication fasst drei Annotationen zusammen:

  • @ComponentScan sucht in diesem Paket und allen Unterpaketen nach Klassen mit Annotationen wie @RestController und erzeugt davon Objekte. Diese von Spring verwalteten Objekte heißen Beans. Klassen außerhalb von de.dbh.mitfahrlabor findet Spring deshalb nicht.

  • @EnableAutoConfiguration schaut nach, welche Bibliotheken vorhanden sind, und richtet sie mit sinnvollen Voreinstellungen ein. Weil Tomcat und Jackson im Klassenpfad liegen, startet Spring einen Webserver auf Port 8080 und wandelt Java-Objekte automatisch in JSON um.

  • @SpringBootConfiguration kennzeichnet die Klasse als Quelle von Konfiguration.

Dieses Prinzip heißt Konvention vor Konfiguration: Was üblich ist, passiert ohne eine Zeile Konfiguration. Abweichungen trägst Du in application.properties ein; im Moment steht dort nur der Name der Anwendung.

Der erste Start

Starte die Anwendung über die Entwicklungsumgebung (in der Startklasse auf „Run“) oder in der Shell im Projektordner:

.\gradlew.bat bootRun

Nach einigen Sekunden erscheinen im Log unter anderem diese Zeilen:

o.s.boot.tomcat.TomcatWebServer          : Tomcat started on port 8080 (http) with context path '/'
d.d.m.MitfahrlaborApplication            : Started MitfahrlaborApplication in 2.131 seconds

Die Anwendung läuft und wartet auf Anfragen. Rufst Du jetzt im Browser http://localhost:8080 auf, erhältst Du eine Fehlerseite mit Status 404: Der Server läuft, kennt aber noch keinen einzigen Endpunkt. Beende die Anwendung mit Strg+C bzw. über den Stopp-Knopf der Entwicklungsumgebung.

Der erste Endpunkt: Haltepunkte von Hand

Laut API-Vertrag liefert GET /v1/haltepunkte eine Liste von Haltepunkten mit den Feldern id, name, ort und entfernungKm. Für diese Daten brauchen wir eine Java-Klasse. Weil sie nur Daten transportiert und sich nie ändert, eignet sich ein Record. Die Klasse kommt in das Paket web, wie im Architekturentwurf festgelegt:

package de.dbh.mitfahrlabor.web;

public record HaltepunktDto(long id, String name, String ort, double entfernungKm) {
}

Diese eine Zeile ersetzt eine ganze Klasse: Der Compiler erzeugt private, unveränderliche Attribute, einen Konstruktor mit allen vier Werten, Zugriffsmethoden (id(), name() …) sowie equals(), hashCode() und toString(). Die Endung Dto steht für Data Transfer Object, ein Objekt, das nur Daten über eine Schnittstelle transportiert.

Nun der Controller. Er hält die sechs Haltepunkte des Szenarios vorerst in einer Liste; ab Artikel 06 kommen sie aus der Datenbank.

package de.dbh.mitfahrlabor.web;

import java.util.List;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/v1/haltepunkte")
public class HaltepunktController {

    private final List<HaltepunktDto> haltepunkte = List.of(
            new HaltepunktDto(1, "Hauptbahnhof, ZOB Steig 3", "Gütersloh", 18.6),
            new HaltepunktDto(2, "Park & Ride am Bahnhof", "Herford", 17.9),
            new HaltepunktDto(3, "Marktplatz", "Halle (Westf.)", 15.2),
            new HaltepunktDto(4, "Busbahnhof", "Bad Salzuflen", 19.4),
            new HaltepunktDto(5, "Kirchplatz", "Werther (Westf.)", 13.1),
            new HaltepunktDto(6, "Bahnhof Brackwede", "Bielefeld", 6.2));

    @GetMapping
    public List<HaltepunktDto> listeHaltepunkte() {
        return haltepunkte;
    }

    @GetMapping("/{haltepunktId}")
    public ResponseEntity<HaltepunktDto> holeHaltepunkt(@PathVariable long haltepunktId) {
        return haltepunkte.stream()
                .filter(h -> h.id() == haltepunktId)
                .findFirst()
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }
}

Die Annotationen erledigen die ganze Arbeit mit HTTP:

  • @RestController macht die Klasse zu einer Bean, die HTTP-Anfragen beantwortet. Rückgabewerte schreibt Spring direkt als JSON in den Body der Antwort.

  • @RequestMapping("/v1/haltepunkte") legt den gemeinsamen Pfad aller Methoden der Klasse fest.

  • @GetMapping ordnet eine Methode der HTTP-Methode GET zu. Ohne Angabe gilt der Pfad der Klasse, mit "/{haltepunktId}" wird ein Platzhalter angehängt.

  • @PathVariable übergibt den Wert aus dem Platzhalter an den gleichnamigen Parameter. Aus /v1/haltepunkte/3 wird haltepunktId = 3.

Die Methode listeHaltepunkte() gibt einfach die Liste zurück; Spring antwortet dann immer mit 200 OK. Bei holeHaltepunkt() muss das Backend auch „nicht gefunden“ sagen können. Dafür gibt die Methode eine ResponseEntity<HaltepunktDto> zurück, mit der Du Statuscode, Header und Body selbst bestimmst. Die Suche nutzt einen Stream: filter() behält nur den Haltepunkt mit der passenden ID, findFirst() liefert ein Optional<HaltepunktDto>, also einen Behälter, der einen Wert enthält oder leer ist. map(ResponseEntity::ok) verpackt einen gefundenen Haltepunkt in eine Antwort mit 200 OK, orElse(…) liefert für einen leeren Behälter 404 Not Found.

Was beim Aufruf im Hintergrund passiert, zeigt das Sequenzdiagramm: Tomcat nimmt die Anfrage an und gibt sie an das DispatcherServlet von Spring weiter. Es sucht anhand der Annotationen die passende Methode, ruft sie auf und lässt das Ergebnis von Jackson in JSON umwandeln.

UML-Sequenzdiagramm einer Anfrage GET /v1/haltepunkte/1: Der Client sendet die Anfrage an Tomcat, Tomcat ruft das DispatcherServlet auf, dieses ruft holeHaltepunkt(haltepunktId=1) des HaltepunktControllers auf und erhält eine ResponseEntity mit Status 200, lässt das HaltepunktDto vom JSON-Konverter in JSON umwandeln und gibt die Antwort zurück an den Client (mit KI erstellt)

Den Endpunkt ausprobieren

Starte die Anwendung erneut. Für einfache GET-Anfragen reicht der Browser: http://localhost:8080/v1/haltepunkte zeigt die Liste als JSON. Statuscodes und Header siehst Du so aber nicht. Besser geeignet ist eine Shell:

In PowerShell heißt das mitgelieferte Programm curl.exe (ohne Endung ruft PowerShell sein eigenes Invoke-WebRequest auf). Die Option -i zeigt Statuszeile und Header mit an:

curl.exe -i http://localhost:8080/v1/haltepunkte
curl.exe -i http://localhost:8080/v1/haltepunkte/1
curl.exe -i http://localhost:8080/v1/haltepunkte/99

# Alternative ohne Header, Ausgabe als Tabelle:
Invoke-RestMethod http://localhost:8080/v1/haltepunkte

Für /v1/haltepunkte/1 antwortet das Backend so:

HTTP/1.1 200
Content-Type: application/json

{"id":1,"name":"Hauptbahnhof, ZOB Steig 3","ort":"Gütersloh","entfernungKm":18.6}

Die Feldnamen im JSON stammen aus den Namen der Record-Komponenten. Probiere auch /v1/haltepunkte/99 (Antwort 404 ohne Body) und /v1/haltepunkte/abc: Weil abc keine Zahl ist, antwortet Spring selbst mit 400 Bad Request, noch bevor Deine Methode aufgerufen wird.

Bequemer als die Shell ist eine .http-Datei. Sie liegt im Projekt, gehört zum Code und lässt sich in IntelliJ IDEA direkt ausführen, in VS Code mit der Erweiterung REST Client:

### Alle Haltepunkte abrufen
GET http://localhost:8080/v1/haltepunkte
Accept: application/json

### Einen Haltepunkt abrufen
GET http://localhost:8080/v1/haltepunkte/1
Accept: application/json

### Unbekannter Haltepunkt (erwartet: 404 Not Found)
GET http://localhost:8080/v1/haltepunkte/99
Accept: application/json

Neben jeder Anfrage erscheint ein Link zum Absenden, die Antwort öffnet sich in einem eigenen Fenster mit Statuscode, Headern und Body.

Der erste Test: ein Smoke-Test

Der Initializr hat schon einen Test angelegt:

package de.dbh.mitfahrlabor;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class MitfahrlaborApplicationTests {

    @Test
    void contextLoads() {
    }
}

Die Testmethode ist leer, und trotzdem prüft sie etwas: @SpringBootTest startet vor dem Test die komplette Anwendung, also Komponentenscan, Autokonfiguration und alle Beans. Scheitert das, etwa weil eine Bean nicht erzeugt werden kann oder die Konfiguration fehlerhaft ist, schlägt der Test fehl. Einen solchen Test nennt man Smoke-Test (Rauchtest): Man schaltet das Gerät ein und schaut, ob Rauch aufsteigt. Ob die Endpunkte richtige Antworten liefern, prüft er nicht. Im V-Modell gehört er zu keiner der vier Testebenen, er ist eine schnelle Vorprüfung. Die richtigen Tests folgen ab Artikel 04.

Ausführen kannst Du ihn in der Entwicklungsumgebung über das Symbol neben der Klasse oder im Build:

.\gradlew.bat test

# Tests ausführen und ausführbare JAR-Datei bauen, dann starten
.\gradlew.bat build
java -jar build\libs\mitfahrlabor-0.0.1-SNAPSHOT.jar

Den Bericht als Webseite findest Du danach bei Gradle unter build/reports/tests/test/index.html, bei Maven als Textdateien unter target/surefire-reports/. Der Befehl build bzw. package führt die Tests übrigens immer mit aus: Schlägt ein Test fehl, entsteht keine JAR-Datei.

Nächster Artikel

Den Controller hast Du von Hand an den Vertrag angepasst: Pfad, Methodennamen und Feldnamen stimmen, weil Du sie abgeschrieben hast. Im nächsten Artikel erzeugt der OpenAPI Generator Schnittstelle und Datenklassen direkt aus openapi.yaml, und der Controller muss sie nur noch implementieren. Weicht der Code dann vom Vertrag ab, merkt es schon der Compiler.

Leitfragen

  1. Welche Einstellungen wählst Du im Spring Initializr für das Mitfahrlabor-Backend, und welche Abhängigkeit brauchst Du für einen REST-Controller?

Build-Tool Gradle (Kotlin) oder Maven, Sprache Java, eine stabile Spring-Boot-Version, Group de.dbh, Artifact mitfahrlabor, Packaging Jar, Java 25 und als Abhängigkeit „Spring Web“, die den Starter spring-boot-starter-webmvc mit Tomcat und Jackson einbindet. siehe Abschnitt „Das Projekt mit dem Spring Initializr erzeugen“

  1. Wozu dient der Wrapper (gradlew bzw. mvnw) im Projektordner?

Der Wrapper lädt beim ersten Aufruf genau die Version des Build-Tools, die das Projekt verlangt, und startet sie. Alle im Team bauen so mit derselben Version, ohne Gradle oder Maven selbst installieren zu müssen. siehe Abschnitt „Was im Projekt steckt“

  1. Was bewirken @ComponentScan und @EnableAutoConfiguration, die in @SpringBootApplication enthalten sind?

@ComponentScan sucht im Paket der Startklasse und allen Unterpaketen nach annotierten Klassen und erzeugt daraus Beans. @EnableAutoConfiguration richtet vorhandene Bibliotheken wie Tomcat und Jackson mit Voreinstellungen ein. siehe Abschnitt „Die Startklasse und die Autokonfiguration“

  1. Was erzeugt der Compiler für record HaltepunktDto(long id, String name, String ort, double entfernungKm)?

Private, unveränderliche Attribute, einen Konstruktor mit allen Werten, Zugriffsmethoden wie id() und name() sowie equals(), hashCode() und toString(). siehe Abschnitt „Der erste Endpunkt: Haltepunkte von Hand“

  1. Warum gibt holeHaltepunkt() eine ResponseEntity zurück und nicht direkt ein HaltepunktDto?

Mit ResponseEntity bestimmt die Methode den Statuscode selbst und kann für eine unbekannte ID 404 Not Found liefern. Gäbe sie direkt ein HaltepunktDto zurück, antwortete Spring immer mit 200 OK, im Fehlerfall mit leerem Body. siehe Abschnitt „Der erste Endpunkt: Haltepunkte von Hand“

  1. Welche Stationen durchläuft die Anfrage GET /v1/haltepunkte/1, bis der Client JSON erhält?

Tomcat nimmt die Anfrage an, das DispatcherServlet sucht über die Annotationen die passende Controller-Methode und ruft sie auf, der JSON-Konverter (Jackson) wandelt das Ergebnis in JSON um, und Tomcat schickt die Antwort an den Client. siehe Abschnitt „Der erste Endpunkt: Haltepunkte von Hand“

  1. Was prüft der Smoke-Test contextLoads() und was nicht?

Er prüft, ob die Anwendung mit allen Beans und der Konfiguration startet. Ob Endpunkte die richtigen Daten und Statuscodes liefern, prüft er nicht. siehe Abschnitt „Der erste Test: ein Smoke-Test“


Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Das Projekt anlegen: der erste Endpunkt für das Mitfahrlabor-Backend” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/spring-boot-projekt-anlegen veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/java-springboot/Backend. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 11.11.2026.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: