Von Code Sentinel & Elyndra Valen, Java Fleet Systems Consulting

Schwierigkeit: 🟡 Mittel
Lesezeit: 25 Minuten
Voraussetzungen: Teil 1 gelesen, Spring Boot 3.x Projekt vorhanden
Serie: Spring Boot 4 Migration (Teil 2 von 3)

Migration

📚 Was bisher geschah – Spring Boot 4 Migration

Bereits veröffentlicht:

  • Teil 1: Was bringt Spring Boot 4? – Neuerungen, Breaking Changes, Entscheidungshilfe

Heute: Teil 2 – Die praktische Migration

Neu in der Serie? Kein Problem!

  • 🟢 Einsteiger? Starte mit Teil 1 für den Überblick
  • 🟡 Erfahren? Du kannst hier einsteigen, wir erklären alles Nötige

⚡ Das Wichtigste in 30 Sekunden

Dein Problem: Du willst migrieren, aber wo fängst du an? Die Fehlermeldungen sind kryptisch und es gibt gefühlt 100 Dinge zu beachten.

Die Lösung: Schritt-für-Schritt Anleitung mit echten Fehlermeldungen und sofortigen Lösungen.

Heute lernst du:

  • ✅ pom.xml / build.gradle richtig updaten
  • ✅ Die 10 häufigsten Compile-Fehler und ihre Fixes
  • ✅ Classic Starters vs. Modulare Starters verstehen
  • ✅ OpenRewrite für automatische Migration nutzen

Für wen ist dieser Artikel?

  • 🌱 Einsteiger: Du siehst, wie eine Migration wirklich abläuft
  • 🌿 Erfahrene: Du bekommst Copy-Paste-Lösungen für jeden Fehler
  • 🌳 Profis: Im Bonus: OpenRewrite und automatisierte Migration

Zeit-Investment: 25 Minuten lesen, 2-4 Stunden für deine Migration


👋 Code Sentinel: „Heute wird’s praktisch!“

Hey! 👋

Code Sentinel hier, mit Elyndra im Backup für die tieferen Erklärungen.

Letzte Woche haben wir über das „Was“ und „Warum“ gesprochen. Heute geht’s ans Eingemachte: Wir nehmen ein Spring Boot 3.5 Projekt und migrieren es auf 4.0. Live. Mit allen Fehlern, die dabei auftreten.

Elyndra: „Ich werde bei jedem Fehler erklären, WARUM er auftritt. Nicht nur wie man ihn fixt – sondern was dahinter steckt.“

Code Sentinel: „Und ich sorge dafür, dass wir einen Rollback-Plan haben. Immer.“

Los geht’s! 🚀


🟢 GRUNDLAGEN

Voraussetzungen checken

Bevor du auch nur eine Zeile änderst:

1. Java-Version prüfen

java -version

Output sollte sein:

openjdk version "17.0.x" oder höher

Spring Boot 4 braucht mindestens Java 17.
Empfohlen: Java 21 oder 25 für Virtual Threads.

2. Spring Boot Version prüfen

Öffne deine pom.xml oder build.gradle:

<!-- Sollte 3.5.x sein! -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.5.8</version>
</parent>

⚠️ WICHTIG: Bist du auf 3.3 oder älter? Dann stopp hier!
Upgrade erst auf 3.5.x. Die direkte Migration von 3.3 → 4.0 ist möglich, aber du überspringst wichtige Zwischenschritte.

3. Backup machen – NICHT OPTIONAL!

# Neuen Branch erstellen
git checkout -b spring-boot-4-migration

# Remote pushen (falls etwas schiefgeht)
git push -u origin spring-boot-4-migration

# Optional: Tag setzen für einfachen Rollback
git tag pre-spring-boot-4

Code Sentinel: „Ich sage das bei jeder Migration: Der Rollback-Plan ist nicht optional. Er ist Teil der Migration.“


Der goldene Pfad

                    ❌ NICHT SO
    ┌─────────────────────────────────────┐
    │  Spring Boot 3.3 ───────► 4.0      │
    │  (zu viele Änderungen auf einmal!)  │
    └─────────────────────────────────────┘

                    ✅ SO MACHEN
    ┌─────────────────────────────────────┐
    │  Spring Boot 3.3 ► 3.5 ► 4.0       │
    │  (stufenweise, kontrolliert)        │
    └─────────────────────────────────────┘

🟡 PROFESSIONALS

Schritt 1: Version ändern

Das ist der einfache Teil. Ändere die Version in deiner Build-Datei:

Maven (pom.xml):

<!-- VORHER -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.5.8</version>
    <relativePath/>
</parent>

<!-- NACHHER -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.0.0</version>
    <relativePath/>
</parent>

Gradle (build.gradle):

// VORHER
plugins {
    id 'org.springframework.boot' version '3.5.8'
    id 'io.spring.dependency-management' version '1.1.6'
}

// NACHHER
plugins {
    id 'org.springframework.boot' version '4.0.0'
    id 'io.spring.dependency-management' version '1.1.7'
}

Schritt 2: Kompilieren und Fehler sammeln

Jetzt kommt der „Moment der Wahrheit“:

# Maven
mvn clean compile 2>&1 | tee migration-errors.log

# Gradle  
./gradlew clean build 2>&1 | tee migration-errors.log

Erwarte Fehler! Das ist normal und gewollt.

Elyndra: „Die Fehlerlog-Datei ist dein Freund. Sie zeigt dir genau, was zu tun ist. Lass dich nicht von der Menge erschrecken – die meisten Fehler haben das gleiche Muster.“


Schritt 3: Die 10 häufigsten Fehler und ihre Fixes

Fehler #1: Package nicht gefunden

Fehlermeldung:

error: package org.springframework.boot.autoconfigure.web.servlet does not exist

Ursache:
Durch die Modularisierung haben sich Package-Namen geändert. Was vorher in org.springframework.boot.autoconfigure war, ist jetzt in spezifischen Modulen.

Fix:

// VORHER
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;

// NACHHER
import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;

Pattern erkennen:

autoconfigure.web.servlet  →  webmvc.autoconfigure
autoconfigure.data.jpa     →  data.jpa.autoconfigure
autoconfigure.security     →  security.autoconfigure

Quick Fix (wenn du keine Zeit hast):

Nutze Classic Starters:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web-classic</artifactId>
</dependency>

Elyndra: „Classic Starters sind wie Stützräder. Sie helfen beim Übergang, aber plane ein, sie später zu entfernen.“


Fehler #2: ConfigurationProperties mit public fields

Fehlermeldung:

Binding to target org.springframework.boot.context.properties.bind.BindException: 
Failed to bind properties under 'app' to com.example.AppConfig

Ursache:
Spring Boot 4 bindet nicht mehr an public fields. Das war schon länger deprecated, jetzt ist es enforced.

Fix:

// ❌ VORHER – funktioniert nicht mehr
@ConfigurationProperties("app")
public class AppConfig {
    public String name;
    public int timeout;
}

// ✅ NACHHER – so muss es sein
@ConfigurationProperties("app")
public class AppConfig {
    private String name;
    private int timeout;
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    
    public int getTimeout() { return timeout; }
    public void setTimeout(int timeout) { this.timeout = timeout; }
}

Noch besser – Java Records (ab Java 16):

@ConfigurationProperties("app")
public record AppConfig(String name, int timeout) { }

Elyndra: „Records sind hier ideal – immutable, kompakt, und du sparst dir den Boilerplate-Code.“


Fehler #3: MockitoTestExecutionListener nicht gefunden

Fehlermeldung:

error: cannot find symbol
symbol: class MockitoTestExecutionListener

Ursache:
MockitoTestExecutionListener wurde in Spring Boot 3.4 deprecated und in 4.0 entfernt. Er wurde nur indirekt genutzt, daher haben viele die Deprecation-Warnung übersehen.

Fix:

// ❌ VORHER
@TestExecutionListeners(MockitoTestExecutionListener.class)
public class MyServiceTest {
    @Mock
    private MyRepository repository;
}

// ✅ NACHHER
@ExtendWith(MockitoExtension.class)
public class MyServiceTest {
    @Mock
    private MyRepository repository;
}

Vergiss nicht den Import:

import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;

Fehler #4: Spring Batch JobRepository fehlt

Fehlermeldung:

No qualifying bean of type 'org.springframework.batch.core.repository.JobRepository' available

Ursache:
Spring Batch 6 (in Boot 4) läuft default im In-Memory-Modus. Job-Metadaten werden nicht mehr automatisch in der DB gespeichert.

Fix (wenn du DB-Metadaten brauchst):

<!-- Ersetze spring-boot-starter-batch durch: -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-batch-jdbc</artifactId>
</dependency>

Fix (wenn In-Memory okay ist): Nichts tun – es funktioniert einfach anders.

Code Sentinel: „Überleg dir gut, was du brauchst. Für viele Anwendungen ist In-Memory völlig ausreichend. Weniger ist manchmal mehr.“


Fehler #5: Property wurde umbenannt

Fehlermeldung:

Property 'spring.data.mongodb.host' is deprecated and will be removed in a future version.
Use 'spring.data.mongodb.uri' instead.

Ursache:
Einige Properties wurden umbenannt oder zusammengefasst.

Temporärer Fix – Properties Migrator:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-properties-migrator</artifactId>
    <scope>runtime</scope>
</dependency>

Dieser zeigt dir beim Start, welche Properties du ändern musst.

Permanenter Fix:

# VORHER
spring:
  data:
    mongodb:
      host: localhost
      port: 27017

# NACHHER
spring:
  data:
    mongodb:
      uri: mongodb://localhost:27017/mydb

Fehler #6: Starter wurde umbenannt

Fehlermeldung:

Could not find artifact org.springframework.boot:spring-boot-starter-xyz

Ursache:
Einige Starter haben neue Namen bekommen.

Bekannte Umbenennungen:

AltNeu
spring-boot-starter-data-mongodbspring-boot-starter-mongodb
spring-boot-starter-actuatorbleibt gleich
spring-boot-starter-webbleibt gleich

Check die Release Notes für die vollständige Liste.


Fehler #7: Auto-Configuration-Klasse hat neuen Namen

Fehlermeldung:

The following classes could not be excluded because they are not auto-configuration classes:
- org.springframework.boot.autoconfigure.xyz.XyzAutoConfiguration

Ursache:
Auto-Configuration-Klassen wurden umbenannt (reactive → non-reactive Konsistenz).

Fix:

// VORHER
@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class
})

// NACHHER – Check den neuen Klassennamen!
@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class  // Meistens gleich, aber verify!
})

Elyndra: „Nutze die IDE-Autovervollständigung. Sie zeigt dir die verfügbaren Klassen im neuen Package.“


Fehler #8: WebSecurityConfigurerAdapter existiert nicht

Fehlermeldung:

error: cannot find symbol
symbol: class WebSecurityConfigurerAdapter

Ursache:
Das hätte schon in Spring Security 5.7 weg sein sollen! Jetzt ist es endgültig entfernt.

Fix:

// ❌ LEGACY – funktioniert nicht mehr
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/public/**").permitAll();
    }
}

// ✅ MODERN – Component-basierter Ansatz
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth
            .requestMatchers("/public/**").permitAll()
            .anyRequest().authenticated()
        );
        return http.build();
    }
}

Code Sentinel: „Das ist der größte Security-Change. Mehr dazu in Teil 3.“


Fehler #9: antMatchers existiert nicht

Fehlermeldung:

error: cannot find symbol
symbol: method antMatchers(String)

Ursache:
antMatchers() wurde durch requestMatchers() ersetzt.

Fix:

// ❌ VORHER
.antMatchers("/api/**").authenticated()
.mvcMatchers("/admin/**").hasRole("ADMIN")

// ✅ NACHHER
.requestMatchers("/api/**").authenticated()
.requestMatchers("/admin/**").hasRole("ADMIN")

Fehler #10: HikariCP Konfiguration

Fehlermeldung:

HikariPool-1 - Failed to validate connection

Ursache:
HikariCP wurde auf Version 7 aktualisiert. Einige Konfigurationsparameter haben sich geändert.

Fix:

# Überprüfe deine application.yml
spring:
  datasource:
    hikari:
      maximum-pool-size: 10
      minimum-idle: 5
      connection-timeout: 30000    # in ms, nicht seconds!
      idle-timeout: 600000
      max-lifetime: 1800000

Schritt 4: Tests laufen lassen

# Maven
mvn test

# Gradle
./gradlew test

Häufige Test-Probleme:

  1. @MockBean verhält sich anders → Explicit Mocking Setup prüfen
  2. TestRestTemplate Ports@LocalServerPort nutzen
  3. Slice Tests → Manche Auto-Configurations sind jetzt in anderen Modulen

Schritt 5: Anwendung starten

# Maven
mvn spring-boot:run

# Gradle
./gradlew bootRun

Checklist nach dem Start:

  • [ ] Alle Endpoints erreichbar?
  • [ ] Actuator /health zeigt UP?
  • [ ] Keine WARN-Logs über deprecated Properties?
  • [ ] Performance ähnlich wie vorher?

🔵 BONUS

OpenRewrite – Automatische Migration

Du kannst 80-90% der Migration automatisieren:

Schritt 1: Plugin hinzufügen

<plugin>
    <groupId>org.openrewrite.maven</groupId>
    <artifactId>rewrite-maven-plugin</artifactId>
    <version>5.42.0</version>
    <configuration>
        <activeRecipes>
            <recipe>org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_5</recipe>
        </activeRecipes>
    </configuration>
    <dependencies>
        <dependency>
            <groupId>org.openrewrite.recipe</groupId>
            <artifactId>rewrite-spring</artifactId>
            <version>5.21.0</version>
        </dependency>
    </dependencies>
</plugin>

Schritt 2: Dry-Run

mvn rewrite:dryRun

Schritt 3: Tatsächlich ausführen

mvn rewrite:run

Elyndra: „OpenRewrite ist mächtig, aber nicht perfekt. Es macht die mechanischen Änderungen – die semantischen musst du selbst prüfen.“


Migrations-Cheat-Sheet

┌─────────────────────────────────────────────────────────┐
│  SPRING BOOT 4 MIGRATION CHEAT SHEET                   │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  1. VOR DER MIGRATION                                   │
│     □ Java 17+ installiert?                             │
│     □ Auf Spring Boot 3.5.x?                            │
│     □ Git Branch erstellt?                              │
│     □ Tests grün?                                       │
│                                                         │
│  2. VERSION ÄNDERN                                      │
│     □ parent version → 4.0.0                            │
│     □ Compile errors sammeln                            │
│                                                         │
│  3. HÄUFIGSTE FIXES                                     │
│     □ Package imports anpassen                          │
│     □ ConfigProperties: private + getter/setter         │
│     □ MockitoExtension statt TestExecutionListener      │
│     □ requestMatchers statt antMatchers                 │
│     □ SecurityFilterChain statt Adapter                 │
│                                                         │
│  4. NACH DER MIGRATION                                  │
│     □ Tests laufen durch?                               │
│     □ Anwendung startet?                                │
│     □ Alle Endpoints erreichbar?                        │
│     □ Performance okay?                                 │
│                                                         │
│  5. ROLLBACK-PLAN                                       │
│     git checkout pre-spring-boot-4                      │
│                                                         │
└─────────────────────────────────────────────────────────┘

💬 Real Talk: Der erste Compile-Versuch

Java Fleet Büro, Dienstag 14:30 Uhr. Nova sitzt vor ihrem Laptop, Elyndra und Code Sentinel schauen über ihre Schulter.


Nova: „Okay, ich hab die Version geändert. mvn clean compile… und…“

(Terminal füllt sich mit roten Fehlermeldungen)

Nova: „Oh nein. Das sind… viele.“

Code Sentinel: „Wie viele?“

Nova: (scrollt) „47 Fehler.“

Code Sentinel: „Das ist weniger als ich erwartet hatte.“

Nova: „Weniger?!“

Elyndra: (setzt sich neben sie) „Lass mich mal sehen. Okay, die meisten sind Import-Fehler. Schau – das gleiche Pattern immer wieder.“

Nova: „Stimmt… alle sagen ‚package does not exist‘.“

Elyndra: „Genau. Das ist die Modularisierung. Die Packages haben sich verschoben. Aber das Gute: Es ist mechanisch. Find-and-Replace.“

Code Sentinel: „Oder OpenRewrite.“

Nova: „Open-was?“

Code Sentinel: „Ein Tool, das diese Änderungen automatisch macht. Zeig ich dir nachher. Aber erstmal: Verstehst du, WARUM die Fehler auftreten?“

Nova: „Weil… Spring Boot jetzt in Modulen organisiert ist? Und die Klassen in anderen Packages liegen?“

Elyndra: (nickt zufrieden) „Genau. Du verstehst das Prinzip. Der Rest ist Fleißarbeit.“

Nova: „Okay. Dann mal los. Eine Frage noch: Wenn ich was kaputt mache…?“

Code Sentinel: „Hast du einen Branch erstellt?“

Nova: „Ja.“

Code Sentinel:git checkout main und du bist wieder am Anfang. Keine Panik möglich.“

Nova: (atmet auf) „Gut. Dann kann ja nichts schiefgehen.“

Elyndra: (lächelt) „Schiefgehen kann immer was. Aber jetzt können wir es kontrolliert kaputtmachen.“


❓ Häufig gestellte Fragen

Frage 1: Muss ich alle Fehler auf einmal fixen?

Nein! Arbeite dich systematisch durch:

  1. Erst: Import-Fehler (meist Copy-Paste)
  2. Dann: Config-Fehler (Properties, ConfigurationProperties)
  3. Zuletzt: Test-Fehler

Committe nach jedem Block. So kannst du bei Problemen teilweise zurückrollen.


Frage 2: Mein Projekt nutzt Spring Cloud. Was muss ich beachten?

Spring Cloud 2025.1.0 ist für Boot 4 gemacht. Update beide zusammen:

<spring-cloud.version>2025.1.0</spring-cloud.version>

Check die Spring Cloud Compatibility Matrix für deine spezifischen Starter.


Frage 3: Die Classic Starters – sind die deprecated?

Ja, aber sie werden nicht sofort entfernt. Sie sind als Übergangslösung gedacht:

  • Kurzfristig: Nutze sie, um schnell lauffähig zu sein
  • Mittelfristig: Migriere auf modulare Starters
  • Langfristig: Classic Starters werden entfernt (vermutlich Boot 5)

Frage 4: Wie lange dauert so eine Migration typischerweise?

ProjektgrößeKlassenGeschätzte Zeit
Klein< 502-4 Stunden
Mittel50-2001-2 Tage
Groß200-5003-5 Tage
Enterprise> 5001-2 Wochen

Code Sentinel: „Plus Puffer für unerwartete Probleme. Immer.“


Frage 5: Was wenn ich auf einen Bug in Spring Boot 4 stoße?

  1. GitHub Issues checken
  2. Wenn bekannt: Workaround nutzen oder auf Patch warten
  3. Wenn unbekannt: Issue erstellen mit minimalem Reproducer
  4. Im Zweifel: Classic Starter als Workaround

Frage 6: Kann ich einzelne Module auf 4.0 upgraden und andere auf 3.5 lassen?

Innerhalb einer Anwendung: Nein. Spring Boot muss konsistent sein.

In einer Microservices-Architektur: Ja! Jeder Service kann unabhängig migriert werden. Das ist sogar empfohlen – migriere Service für Service.


Frage 7: Bernd sagt, er hat mal eine Migration ohne Backup gemacht. Wie ist das ausgegangen?

(Code Sentinel’s Augenbraue zuckt)

Wir reden nicht über den Vorfall von 2021.

Real talk: Bernd ist seitdem der größte Verfechter von git tag pre-migration. Er sagt: „Einmal reicht. Danach lernst du.“

Die Moral: Backup. Immer. Keine Ausnahmen.

Und wenn jemand sagt „Ach, das ist nur eine kleine Änderung“ – dann ERST RECHT Backup. Kleine Änderungen haben die größte Zerstörungskraft. 💥


📦 Downloads

🎯 spring-boot-4-migration-example.zip

  • Vollständiges Vorher/Nachher Projekt
  • Spring Boot 3.5 Version
  • Spring Boot 4.0 Version (migriert)
  • Diff-Dokumentation
📁 spring-boot-4-migration-example/
├── 📁 before-3.5/
│   └── [komplettes Projekt]
├── 📁 after-4.0/
│   └── [migriertes Projekt]
├── MIGRATION-DIFF.md
└── README.md

📋 migration-cheatsheet.pdf

  • Eine Seite, alle Fixes
  • Zum Ausdrucken und neben den Monitor hängen

🔗 Externe Links

Migration Tools:

Offizielle Docs:


🖼️ Grafiken in diesem Artikel

  • 03_modularisierung_vergleich.svg – Vorher/Nachher der Module
  • 04_migration_prozess.svg – Die 5 Schritte mit häufigen Fehlern

🎯 Wie geht’s weiter?

Teil 3: Spring Security 7 Migration

Der Teil, vor dem sich alle fürchten. Aber keine Sorge – Code Sentinel nimmt dich an die Hand und erklärt jeden Schritt. Security muss nicht schmerzhaft sein.

Erscheint: Dienstag


Code Sentinel ist Technical Project Manager und Security-Experte bei Java Fleet. Er sorgt dafür, dass Migrationen kontrolliert ablaufen – mit Rollback-Plan und ohne Panik.

Elyndra Valen ist Senior Software Architect bei Java Fleet. Sie erklärt das „Warum“ hinter den Fehlern, damit du nicht nur fixst, sondern verstehst.

Fragen? Feedback?
code.sentinel@java-developer.online | elyndra.valen@java-developer.online


Teil 2 von 3 der Spring Boot 4 Migration Serie
java-developer.online • Dezember 2025

Autoren

  • Code Sentinel

    32 Jahre alt, Technical Project Manager und Security-Experte bei Java Fleet Systems Consulting. Code ist ein erfahrener Entwickler, der in die Projektleitung aufgestiegen ist, aber immer noch tief in der Technik verwurzelt bleibt. Seine Mission: Sicherstellen, dass Projekte termingerecht, sicher und wartbar geliefert werden.

  • Elyndra Valen

    28 Jahre alt, wurde kürzlich zur Senior Entwicklerin befördert nach 4 Jahren intensiver Java-Entwicklung. Elyndra kennt die wichtigsten Frameworks und Patterns, beginnt aber gerade erst, die tieferen Zusammenhänge und Architektur-Entscheidungen zu verstehen. Sie ist die Brücke zwischen Junior- und Senior-Welt im Team.