Veröffentlicht am
15.5.2026
Framework-Freitag: Java Spring Boot
Java Spring Boot gehört wohl zu den wichtigsten und gleichzeitig auch mächtigsten Enterprise-Frameworks, die es gibt. Es ist in etwa vergleichbar mit Node.js Express oder PHP Symfony, mit dem REST-APIs erstellt werden können. Das Framework bietet langfristige Stabilität, Wartbarkeit und die Integration in komplexe Infrastrukturen, da Java anders als PHP oder JavaScript bereits sehr stark typisiert ist. Natürlich kann ich nicht alle Features von Spring Boot in diesem Artikel erläutern, daher bleibe ich bei den Grundlagen.
Ein kurzer Rückblick
Java bildete schon vor dem eigentlichen Erscheinen von Spring Boot das Rückgrat vieler Enterprise-Anwendungen, wobei es heute nach wie vor komplexe Software in COBOL gibt, die nach und nach in Java migriert werden. Bevor es Spring gab, nutzte man J2EE. Das steht für Java 2 Enterprise Edition und die Entwicklung damit war ziemlich schwerfällig, kompliziert zu testen und man musste vieles in sehr großen XML-Dateien konfigurieren. Rod Johnson veröffentlichte im Jahre 2002 sein Buch “Expert One-on-One J2EE Design and Development”, worin er die Komplexität von J2EE kritisierte. Anhand der Codebeispiele ist das Spring Framework entstanden und mithilfe von Dependency Injection, kurz: DI und Inversion of Control, kurz: IoC war Java-Code wieder leichtgewichtig, modular und vor allem testbar geworden. Doch mit der Zeit wuchs auch das Spring Framework immer weiter an und man musste sich wieder durch XMLs quälen: Abhängigkeiten via Maven verwalten, einen Webserver wie Tomcat installieren oder Datenbankkonfigurationen schreiben - das geschah zu 90% in langen XMLs mit vielen Zeilen Code. 2014 erschien jedoch Spring Boot unter dem Team von Pivotal und die Anwendungen können deutlich einfacher, schneller und sicherer konfiguriert werden wie früher. In der heutigen Zeit von Domain-Driven-Development, Microservices und Cloud-Computing ist Spring Boot nach wie vor sehr beliebt.
Die Grundlagen
Eine Spring Boot Anwendung kann beispielsweise die folgende Projektstruktur haben:
src/
├── main/
│ ├── java/com/example/demo/
│ │ ├── DemoApplication.java <-- Der Haupteinstiegspunkt
│ │ ├── controller/ <-- REST-Endpunkte
│ │ ├── service/ <-- Businesslogik
│ │ ├── repository/ <-- Datenbank-Interfaces
│ │ └── model/ <-- Datenbank-Entitäten
│ └── resources/
│ ├── application.properties <-- Konfiguration (DB-Url, Ports, usw.)
│ ├── static/ <-- Statische Dateien (JS, CSS, usw.)
│ └── templates/ <-- HTML-Templates (z.B. Thymeleaf)
└── test/ <-- Unit- und Integrationstests
In dem Haupteinstiegspunkt befindet sich die Main-Methode, mit der die ganze Anwendung gestartet wird. Dieser kann als Klasse beispielsweise so aussehen:
package com.tennismeister.tennismeister;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TennismeisterApplication {
/**
* The entry point of the Spring Boot application.
*
* @param args command-line arguments passed to the application
*/
public static void main(String[] args) {
SpringApplication.run(TennismeisterApplication.class, args);
}
}
Spring Boot arbeitet mit vielen so genannten Annotationen wie @Service, @RestController oder @Entity. Auch in dem hier gezeigten Haupteinstiegspunkt findest du die Annotation @SpringBootApplication, womit die tatsächliche Spring Boot Anwendung gestartet wird. Beim Start durchsucht das Framework alle Unterpakete nach Klassen mit den zuvor genannten Annotationen und erstellt daraus mithilfe von Dependency Injection Instanzen - so genannte Beans - und “verknüpft” diese miteinander. Wenn ein Service ein Repository braucht, wird dieser automatisch in den Konstruktor “injiziert”. Außerdem erkennt das Framework anhand der Konfigurationsdateien, welche Bibliotheken genutzt werden und bereitet diese im Hintergrund automatisch vor. Eine bei Gradle beliebte Konfigurationsdatei ist die build.gradle.kts, welche in Kotlin geschrieben ist. .kts steht hierbei für Kotlin Script. Sie kann beispielsweise so aussehen:
plugins {
java
id("org.springframework.boot") version "3.5.9"
id("io.spring.dependency-management") version "1.1.7"
checkstyle
}
group = "com.tennismeister"
version = "0.0.1-SNAPSHOT"
description = "Backend for the Tennismeister application"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
checkstyle {
configFile = rootProject.file("config/checkstyle/checkstyle.xml")
toolVersion = "12.2.0"
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.flywaydb:flyway-core")
implementation("org.flywaydb:flyway-mysql")
implementation("io.jsonwebtoken:jjwt-api:0.12.6")
developmentOnly("org.springframework.boot:spring-boot-devtools")
runtimeOnly("org.mariadb.jdbc:mariadb-java-client")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.boot:spring-boot-testcontainers")
testImplementation("org.testcontainers:junit-jupiter")
testImplementation("org.testcontainers:mariadb")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.withType<Test> {
useJUnitPlatform()
}
Oder so, wie die application.yml im Resources-Verzeichnis:
spring:
application:
name: tennismeister
datasource:
url: jdbc:mariadb://localhost:3306/tennismeister
username: tennismeister
password: tennismeister
driver-class-name: org.mariadb.jdbc.Driver
jpa:
open-in-view: false
hibernate:
ddl-auto: validate
properties:
hibernate:
dialect: org.hibernate.dialect.MariaDBDialect
jdbc:
time_zone: UTC
flyway:
enabled: true
locations: classpath:db/migration
jackson:
time-zone: UTC
logging:
level:
org.flywaydb: DEBUG
org.springframework.boot.autoconfigure.flyway: DEBUG
Anhand dieser Konfigurationsdateien konfiguriert Spring Boot im Hintergrund die Bibliotheken vor und man sich ganz bequem um die eigentliche Entwicklung kümmern. Ich persönlich habe beim Erlernen von Spring Boot sehr viele Parallelen beim Laravel-Framework gesehen, da man bei beiden Frameworks den selben Ansatz verfolgen kann. Spring Boot ist meiner Meinung nach jedoch deutlich strenger und genauer bei der Konfiguration und man muss vieles selber implementieren, da Spring Boot nicht alles sofort von Haus aus direkt dabei hat, wie z.B. die Authentifizierung bei Laravel.
Der Ablauf eines Datenflusses
Nehmen wir an, ein Nutzer möchte seine Profildaten abrufen:
- Der Browser des Benutzers sendet einen GET-Request unter der URL /user/1.
- Der Controller nimmt die ID 1 entgegen und ruft z.B. die Methode
userService.findUser(1)auf. - Der Service überprüft, ob der Nutzer überhaupt berechtigt ist, die Daten von User 1 überhaupt zu sehen. Es könnte sich schließlich auch um einen anderen User handeln, dessen Daten er via Postman auslesen möchte.
- Danach werden die Daten aus dem Repository aus der Datenbank abgefragt und als Entity-Objekt zurückgegeben.
- Der Service gibt das Objekt anschließend an den Controller zurück, dieser wandelt es mithilfe eines Converters - üblicherweise Jackson - in ein JSON um und schickt es an den Browser.
Und nun mit Code…
Ein Codebeispiel sagt bekanntermaßen mehr als 1000 Worte. Wir beginnen in der CourtController.java, welche die Anfrage des Browsers für die Anzeige der verfügbaren Tennisplätze entgegen nimmt:
/**
*
* Returns a paginated list of courts.
* <p>
* If a {@code clubId} is provided, only courts belonging to the specified club are returned.
* Otherwise, all courts are retrieved.
* </p>
*
* @param clubId optional ID of the club to filter courts by; can be {@code null}
* @param pageable object containing pagination and sorting information
* @return a {@link Page} of {@link CourtResponse} containing the retrieved courts
*/
@GetMapping("/courts")
public Page<CourtResponse> listCourts(
@RequestParam(required = false) Long clubId,
Pageable pageable
) {
return courtService.listCourts(clubId, pageable);
}
Anschließend geht es in der CourtService.java, welche auf das entsprechende Repository zugreift:
/**
* Returns a paginated list of courts belonging to a specific club.
*
* @param clubId the ID of the club whose courts should be listed
* @param pageable the pagination and sorting information
* @return a {@link Page} of {@link CourtResponse} objects for the requested club
*/
@Transactional(readOnly = true)
public Page<CourtResponse> listCourts(Long clubId, Pageable pageable) {
Page<CourtEntity> page = (clubId == null)
? courtRepository.findAll(pageable)
: courtRepository.findByClubId(clubId, pageable);
return page.map(courtMapper::toResponse);
}
In der CourtRepository.java werden aus der Datenbank die entsprechenden Tennisplätze ausgelesen. Spring Boot formuliert aus den Namen der Methoden automatische SQL-Abfragen, sodass findAll() erst gar nicht implementiert werden musste. Dieses wurde nämlich von der Klasse JpaRepository geerbt.
/**
* Finds all courts that belong to the specified club in a paginated manner.
*
* @param clubId the ID of the club whose courts should be retrieved
* @param pageable the pagination and sorting information
* @return a {@link Page} of {@link CourtEntity} objects for the given club
*/
Page<CourtEntity> findByClubId(Long clubId, Pageable pageable);
Anschließend wird in der CourtMapper.java das zurückgegebene Entity-Objekt zu einer Antwort gemappt und der Service bzw. der Controller gibt diese dann an den Browser zurück:
/**
* Maps a {@link CourtEntity} to a {@link CourtResponse} DTO.
*
* @param courtEntity the {@link CourtEntity} to be mapped
* @return a {@link CourtResponse} containing the data of the given entity
* @throws NullPointerException if {@code courtEntity} or {@code courtEntity.getClub()} is {@code null}
*/
public CourtResponse toResponse(CourtEntity courtEntity) {
return new CourtResponse(
courtEntity.getId(),
courtEntity.getClub().getId(),
courtEntity.getName(),
courtEntity.getNumber(),
courtEntity.getType().name(),
courtEntity.isInside(),
courtEntity.getCreatedAt(),
courtEntity.getUpdatedAt()
);
}
Das hier ist nur ein kleines und überschaubares Beispiel in Spring Boot. In großen Enterprise-Anwendungen sind viele Services mit sehr vielen Zeilen Code mit Microservices keine Seltenheit und man muss sich erst einmal in die Anwendung hinein fuchsen, um einen Überblick zu bekommen, was wie wo wann passiert. Aber mit einer guten Dokumentation und entsprechenden Kommentaren ist auch das kein großes Hexenwerk.
Fazit
Ich habe in dem Artikel nur die Grundlagen von Spring Boot anhand eines einfachen Beispiels einer kleinen REST-API beleuchtet, obwohl Spring Boot weitaus mächtiger und umfangreicher ist. Man kann ähnlich wie bei Laravel Migrationen zur Versionierung von Datenbanken erstellen oder genau mit Methoden festlegen, wie sich die Anwendung bei Exceptions verhalten soll oder die ganzen Unit Tests in speziellen Docker-Containern laufen lassen. Nicht umsonst wird dieses Framework in großen Unternehmen wie Banken und Versicherungen sehr häufig verwendet. Der Einstieg kann einen etwas von dem Umfang her erschlagen und man hat das Gefühl, es mit einem allmächtigen Gott zu tun zu haben. Wenn man jedoch einmal den Einstieg geschafft hat, bekommt man schnell ein Gefühl für große Enterprise-Anwendungen.