Erfahrungsbericht Spring Boot 3 Update

Unser Java-System ist jetzt mit Spring Boot Version 3 im produktiven Einsatz! Hier teile ich meinen Erfahrungsbericht, da sich Systeme in der echten Welt häufig von Demos oder Tutorials unterscheiden.

Highlights Spring Boot 3

Warum auf Spring Boot 3 und damit auf die Version 6 des Spring Frameworks updaten?
  • Spring 6 ist auf die aktuelle Java LTS Version 17 aktualisiert. Damit modernisiert Spring sich in erster Linie selbst, wovon wir indirekt profitieren.
  • Native Images mit GraalVM werden offiziell supported. Das ermöglicht unserer Spring Anwendung den blitzschnellen Start als Docker Container, siehe graalvm.html.
  • Viele direkt oder indirekt verwendete 3rd Party Bibliotheken sind auf neue Versionen aktualisiert. Wir bekommen damit diverse Fixes für Sicherheitslücken.

Planung des Spring Boot 3 Updates

Der Spring Boot 3.0 Migration Guide enthält im Wesentlichen diese Schritte:
  1. Update auf Spring Boot 2.7
  2. Update auf Java 17
  3. Update auf Spring Boot 3
  4. Dependencies anpassen
  5. Code anpassen
Unsere Ausgangssituation war eine Spring Boot Anwendung in Version 2.7.X mit Java 17. Alle wesentlichen Dependency-Versionen managt Spring Boot für uns. Außerdem haben wir automatisierte Ende zu Ende Tests, die nach dem Update und der Beseitigung aller Compile-Fehler die Funktionalität unseres Systems schnell testen. Durch frühes Ausprobieren von Spring Boot 3 an einfachen Demo-Projekten wussten wir, dass die API Code Generatoren und die Spring Security Konfiguration Code-Anpassungen benötigen. Daher planten wir unser Spring Boot 3 Update so:
  1. Umstellung des Maven Swagger Code Generator Plugins auf OpenAPI Code Generator.
  2. Aktivierung des Spring Boot 3 Supports im OpenAPI Code Generator.
    Zum Zeitpunkt unserer frühen Migration hatte der Swagger Code Generator Spring Boot 3 bzw. die jakarta Package imports nicht generiert. Hier hätten wir auch warten können.
  3. Spring Boot Version 3.0.X in Maven Parent POM konfigurieren.
  4. Code Anpassung jakarta statt javax Imports
  5. Code Anpassung in Spring Security Konfiguration
Akzeptanz-Kriterium: Das Spring Boot 3 Update ist erst erfolgreich, wenn:
    • es keine Compile-Fehler gibt
    • alle JUnit-Tests erfolgreich durchlaufen
    • die CICD Pipeline alle Ende zu Ende Tests erfolgreich ausgeführt hat

Praktische Umsetzung des Spring Boot 3 Updates

Unser System wird seit über zwei Jahre von 6 EntwicklerInnen gebaut und befindet sich über ein Jahr im produktiven Einsatz. Daher bescherte uns die Umsetzung einige ungeplante Überraschungen. Diese Mehraufwände stelle ich im Folgenden thematisch gruppiert vor.

REST API Code Generator

Den Code für REST-APIs generieren wir mittlerweile mit dem OpenAPI Maven Plugin. Das grundsätzliche Vorgehen um den OpenAPI Code Generator mit Spring Boot 3 zu verwenden, stelle ich in diesem Artikel vor: openapi-codegen.html

Das Problem in der Praxis war, dass wir vom Swagger Code Generator auf den OpenAPI Code Generator wechseln mussten. Also nicht einfach nur eine neuere Version verwenden, sondern auch die Technologie austauschen. Für APIs in guter Qualität hat das problemlos funktioniert. Da unser System über 10 verschiedene REST-APIs benutzt, haben wir leider auch APIs in schlechter Qualität für die der OpenAPI Code Generator nur mit Spezial-Konfigurationen oder Workarounds funktioniert.

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>6.2.1</version>
    <executions>
        <execution>
    <id>x-service</id>
    <goals>
<goal>generate</goal>
    </goals>
    <configuration>
<inputSpec>.../x-api.yml</inputSpec>
<generatorName>spring</generatorName>
<configOptions>
         ...
    <useSpringBoot3>true</useSpringBoot3>
</configOptions>
<skipValidateSpec>true</skipValidateSpec>
...
    </configuration>
</execution>
... 

  • Für Code Generierung mit Spring Boot 3 setzen wir <useSpringBoot3>
  • Schlechte API Qualität zwingt uns die Validierung der API Spezifikation im Maven Plugin zu überspringen: <skipValidateSpec>

SOAP API Code Generator

Unser System verwendet die SOAP API eines Legacy Systems. In unserer Planung hatten wir diese Schnittstelle vergessen. In der Praxis gab es das gleiche Problem wie beim Generieren der REST APIs unser Plugin unterstützt Spring Boot 3 noch nicht.

Die Lösung fanden wir im GitHub Projekt unseres maven-jaxb2-plugin. Dort wurde ein Plugin aus einem anderen Branch empfohlen. Ohne auf weitere Details der SOAP Code Generierung einzugehen, es sieht jetzt so aus:

<plugin>
    <groupId>com.helger.maven</groupId>
    <artifactId>jaxb40-maven-plugin</artifactId>
    <version>0.16.1</version>
    <executions>
        <execution>
    <goals>
<goal>generate</goal>
            </goals>
</execution>
    </executions>
    <configuration>
<schemaLanguage>WSDL</schemaLanguage>
<generatePackage>...</generatePackage>
<schemaDirectory>...</schemaDirectory>
<schemaIncludes>
    <include>y-service.wsdl</include>
</schemaIncludes>
    </configuration>
</plugin>


Code Anpassungen

Nach dem Spring Boot 3 Dependency Update zeigte der Compiler viele Fehler an. Erwartungsgemäß ersetzen wir dazu javax durch jakarta Imports - mit der Text suchen und ersetzen Funktion der IDE geht das recht schnell. Hier ein paar Beispiele:
  • import javax.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletRequest;
  • import javax.annotation.PostConstruct;
    import jakarta.annotation.PostConstruct;
  • import javax.validation.Valid;
    import jakarta.validation.Valid;
Spring 6 führt das Interface HttpStatusCode neu ein. Dieses wird nun von diversen Klassen und Methoden im Spring Framework anstelle des enum HttpStatus verwendet. Das ist eine überschaubare Umstellung, weil HttpStatus das Interface HttpStatusCode implementiert. Trotzdem müsst ihr die Stellen im Code anpassen, welche jetzt nur noch das Interface zur Verfügung haben:

WebClient.create("url").get().retrieve()
    .onStatus(HttpStatus::isError, ...)...
    .onStatus(HttpStatusCode::isError, ...)...

Spring Security Code Anpassungen

Spring Security 6 entfernte Methoden und ersetzte sie durch neue. Dadurch entstehen beim Spring Boot 3 Update Compile Fehler. In meinem Einsteiger Artikel zu Spring Security 6 zeige und erkläre ich aktuellen Code, daher hier nur die Vorher-Nachher Gegenüberstellung:

@EnableWebSecurity
// Before @EnableWebSecurity included @Configuration
@Configuration
public class SecurityConfiguration {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) {
        http.authorizeRequests()
        http.authorizeHttpRequests()
            .antMatchers("/secured").authenticated()
            .requestMatchers("/secured").authenticated()
    .anyRequest().permitAll()
        ...

Wir verwenden für manche API Tests Spring MockMvc. Diese Tests haben nach dem Spring Boot 3 Update das Problem, dass die Spring Security Filter automatisch angewendet werden und Requests wegen fehlender Authentifizierung ablehnen. Hier ein Vorher-Beispiel:

@SpringBootTest
@AutoConfigureMockMvc
class MockMvcTest {

    @Autowired private MockMvc mvc;

    @Test
    void redirectTest() throws Exception {
        mvc.perform(get("/secured"))
            .andExpect(status().is3xxRedirection());
    }
}

Anstelle des erwarteten Http Status 3XX lehnt die Spring SecurityFilterChain Bean den Request ab. Ein einfacher Weg die Security Filter zu entfernen, bietet das Attribut addFilters der Annotation @AutoConfigureMockMvc:

@SpringBootTest
@AutoConfigureMockMvc(addFilters = false)
class MockMvcTest { ...

Spring Web-Flow Workaround

Aus historischen Gründen verwendet unsere Anwendung Spring Web-Flow. Ich empfehle Spring Web-Flow nicht, weil es nach der Version 2.5.1 über 4 Jahre kein Update erhielt. Nach dem Update auf Spring Boot 3 kam es im Web-Flow Code zu Compile Fehlern. Zum Zeitpunkt dieses Artikels war Spring Web-Flow 3.0.0 passend zu Spring Boot 3 noch nicht im zentralen Maven Repository verfügbar. Um das Spring Boot 3 Update trotzdem durchführen zu können, entschieden wir uns Spring Web-Flow als Milestone Release 3.0.0-M1 zu verwenden. Dazu hinterlegen wir das Spring Milestone Repository als weiteres Maven Repository in der pom.xml:

    <dependency>
        <groupId>org.springframework.webflow</groupId>
<artifactId>spring-webflow</artifactId>
<version>3.0.0-M1</version>
    </dependency>
...

<repositories>
    <repository>
        <id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
    <enabled>false</enabled>
</snapshots>
    </repository>
</repositories>

Ein weiteres Problem lag im Zusammenspiel von Spring Security 6 und Spring Web-Flow 3. Mit Spring Security 5 gab es ein Default Attribute "ROLE_USER" im OpenID Connect Token. Dessen Existenz  überprüften wir in der Spring Web-Flow 2.5.1 XML-Konfiguration so:

    <secured attributes="ROLE_USER" />

Nachdem Spring Security 6 Update gibt es andere Default Attribute. Eines hat den Namen "OIDC_USER". Trotz folgender Anpassung funktionierte die Token Überprüfung per XML-Konfiguration im Web-Flow nicht:

    <secured attributes="OIDC_USER" />

Der Grund dafür ist, dass Spring Web-Flow den Prefix "ROLE_" erwartet. Diesen Prefix gibt es aber in den Default Attributen des Tokens nicht mehr. Daher habe ich die Methode getRolePrefix() im RoleVoter überschrieben, um den Prefix "ROLE_" durch den leeren String "" zu ersetzen. Den RoleVoter setze ich im AccessDecisionManager des SecurityFlowExecutionListener, diesen hatten wir schon mit Spring Web-Flow 2.5.1 in einer Bean instanziiert.

@Bean
public FlowExecutor flowExecutor() {
    var listener = new   SecurityFlowExecutionListener();
    listener.setAccessDecisionManager(new ConsensusBased(
        List.of(
            new RoleVoter() { 
        @Override
             public String getRolePrefix() {
            return "";
                }
    }
        )
    ));
    
    return getFlowExecutorBuilder(flowRegistry())
        .addFlowExecutionListener(listener)
        .build();
}

Fazit

Die Komplexität des Spring Boot 3 Updates hängt von der Größe eurer Anwendung und der Anzahl nicht durch Spring Boot gemanagter Bibliotheken ab. Insbesondere wenn die Bibliotheken dem aktuellen Stand der Technik hinterherhinken, wie in meinem Fall z. B. Spring Web-Flow, wird es kompliziert und dreckig. 

Die Workarounds, frühe Beta-Versionen oder alternative Bibliotheken (hier OpenAPI statt Swagger Code Generator) zu verwenden, sind riskant. Dank vielen Unit-Tests und guten, automatisierten Ende zu Ende entschieden wir uns für das Update trotz der genannten Risiken. Heute profitieren wir von den Vorteilen der neusten Spring Version.

Kommentare

Beliebte Posts aus diesem Blog

Authentifizierung in Web-Anwendungen mit Spring Security 6

OpenID Connect mit Spring Boot 3