Migratie naar Java 25 en Spring Boot 4: eerlijk, het was chaos
“I’m ready… to break everything and figure it out later!” – SpongeBob SquarePants
Iedereen zegt altijd dat upgraden van Spring makkelijk is, toch? Gewoon OpenRewrite draaien, een paar kleine dingen fixen en klaar.
Nou… laat me vertellen over onze migratie van Java 17/21 naar Java 25 en Spring Boot 3 naar 4.0.1.
Voor één service kost het nu maximaal één uur om alles te fixen. Niet zo erg. Maar ik heb deze migratie al een paar keer gedaan bij de klant. En ik blijf steeds dezelfde problemen tegenkomen, keer op keer.
Dus ik dacht: laat ik dit opschrijven. Misschien helpt het iemand anders die deze migratie moet doen.
Waarom we dit überhaupt deden
Onze tech lead wilde naar Java 25. Zijn reden? Hij wil altijd de nieuwste versie van alles. En eerlijk, ik was wel enthousiast om eens een migratie mee te maken.
Daarnaast moesten we:
- Upgraden naar Spring Boot 4 en Spring Framework 7
- De Jakarta-migratie volledig afronden
- Oude code opruimen voordat we nieuwe features toevoegen
Onze applicaties bevatten allemaal:
- REST APIs
- Custom HTTP clients
- JWT security
- File uploads
- Heel veel tests met MockMvc
- Jackson voor JSON overal
OpenRewrite: helpt, maar is geen magie
We begonnen met OpenRewrite en de officiële Spring recipes (bron).
Wat Rewrite goed deed:
javax.*→jakarta.*- Spring imports updaten
- Deprecated APIs fixen
- Config classes aanpassen
- Try-catch syntax vereenvoudigen
.additionalMessageConverters(new MappingJackson2HttpMessageConverter())→.additionalMessageConverters(new JacksonJsonHttpMessageConverter()).format(...)→.formatted(...)- YAML splitsen per profiel
Maar… er kwamen ook snel problemen.
OpenRewrite + JDK/JRE mismatch
Eerste keer dat ik OpenRewrite draaide op Java 25 crashte het met:
java.lang.LinkageError: loader constraint violation: com.sun.tools.javac.parser.Tokens$Comment$CommentStyle
Oorzaak: JDK en JRE waren niet dezelfde versie.
Fix:
- Check altijd dat JDK en JRE dezelfde versie hebben
- Voor ons: beide op Java 25 zetten
“Alles compileert!” … dan exploderen de tests
Na Rewrite en alle compileerfouten fixen, startten we de tests. En dan? Alles faalde.
Problemen zaten vooral in:
- MockMvc tests
- JSON serialization
- HTTP clients
- Jackson config
Jackson: de pijn die nooit stopt
Spring Boot 4 verhuist Jackson van:
Oude locatie:
com.fasterxml.jackson.*
Nieuwe locatie:
tools.jackson.*
Problemen die we tegenkwamen:
- Mixed imports in tests
- Constants zoals
WRITE_DATES_AS_TIMESTAMPSverdwenen - ObjectMapper conflicts, vooral in tests met meerdere primary beans
Vroeger hadden we:
@Bean
@Primary
ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
return builder.dateFormat(...).build();
}
In Boot 4:
Jackson2ObjectMapperBuilderis deprecated- Boot maakt zijn eigen primary
jacksonJsonMapper
Resultaat:
NoUniqueBeanDefinitionException:
more than one 'primary' bean found: [objectMapper, jacksonJsonMapper]
Nieuwe aanpak:
@Bean
public ObjectMapper objectMapper() {
var dateFormat = new SimpleDateFormat("dd-MM-yyyy");
return JsonMapper.builder()
.defaultDateFormat(dateFormat)
.findAndAddModules()
.build();
}
Bron: Introducing Jackson 3 support in Spring
RestTemplate & HTTP veranderingen
RestTemplateBuilder verplaatst
Oude locatie:
org.springframework.web.client.RestTemplateBuilder
Nieuwe locatie:
org.springframework.boot.restclient.RestTemplateBuilder
In productiecode migreer je vaak naar RestClient, Spring Boot 4 geeft hier voorkeur aan.
Response API veranderd
Oude manier:
response.getStatusCodeValue()
Nieuwe manier:
response.getStatusCode().value()
Headers vervangen
Oude Apache manier:
Header[] headers = response.getAllHeaders();
Nieuwe Spring manier:
HttpHeaders headers = response.getHeaders();
Timeouts zijn nu builders
Oude setters bestaan niet meer. Nieuw:
HttpComponentsClientHttpRequestFactory requestFactory =
ClientHttpRequestFactoryBuilder.httpComponents()
.withDefaultRequestConfigCustomizer(rc -> rc
.setConnectTimeout(Timeout.ofSeconds(5))
.setConnectionRequestTimeout(Timeout.ofSeconds(5))
.setResponseTimeout(Timeout.ofSeconds(5))
)
.build();
Custom HttpClients moeten nu via .withHttpClientCustomizer(...) worden toegevoegd.
Bron: Spring Framework 7 HTTP Client
Apache HttpClient 4 → 5
Alles is veranderd:
org.apache.http.*→org.apache.hc.*- Timeouts, SSL, connection managers, request builders → volledige rewrite
Bron: Apache HttpClient 5 Migration
Swagger & OpenAPI na Jakarta
Na migratie verdwijnen OpenAPI annotaties. Voeg toe:
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations-jakarta</artifactId>
</dependency>
Bron: Swagger
Spring Security 7 WebSocket
AbstractSecurityWebSocketMessageBrokerConfigurer is verwijderd. Voor ons werkt deze aanpak:
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.config.annotation.web.messaging.EnableWebSocketSecurity;
import org.springframework.security.messaging.access.intercept.MessageAuthorizationContext;
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
@Configuration
@EnableWebSocketSecurity
public class WebSocketSecurityConfig {
@Bean
public AuthorizationManager<Message<?>> messageAuthorizationManager(
MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages
.nullDestMatcher().permitAll()
.simpSubscribeDestMatchers("/**").permitAll()
.simpDestMatchers("/**").permitAll()
.anyMessage().permitAll();
return messages.build();
}
@Bean
public AuthorizationManager<MessageAuthorizationContext<?>> authorizationManager() {
return (_, _) -> new AuthorizationDecision(true);
}
}
Bron: Spring Security 7 WebSocket, Github Thread
Terugkerende problemen
Tijdens de migratie kwamen de meeste problemen steeds terug, vooral in tests:
- Jackson namespace breekt constant
- ObjectMapper conflicts in tests
- HTTP client configuratie moet volledig herschreven worden
- Response handling methoden gewijzigd
- RestTemplate en builders hebben nieuwe imports of zijn vervangen door RestClient
- Tests falen vaak ondanks succesvolle compilatie
Daarnaast blijkt dat compiler, serialisatielaag, SSL-configuratie en auto-configuration allemaal tegelijk worden beïnvloed door de upgrade.
Wat ik zou aanraden
- Plan extra tijd voor tests
- Verwacht Jackson-problemen en ObjectMapper-conflicten
- Bereid je voor op herschrijven van HTTP client code
- Run je volledige test suite vroeg en vaak
- Gebruik OpenRewrite
Persoonlijk ben ik blij dat ik deze migratie zelf mocht doen. Ondanks de frustraties en het vele lezen van documentatie, gaf het me een veel dieper inzicht in hoe Spring Boot en Spring Framework werken en hoe ze de controle over infrastructuur en configuratie hebben.
Wil je meer weten over Spring Boot 4 en Spring Framework 7? Check dan volgende post: Spring Boot 4 & Spring Framework 7.
Overzicht van alle bronnen
| Resource | Link |
|---|---|
| OpenRewrite recipes | https://docs.openrewrite.org/recipes/java |
| Spring Framework 7 HTTP Client | https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-request-factories |
| Apache HttpClient 5 Migration | https://hc.apache.org/httpcomponents-client-5.6.x/migration-guide/preparation.html |
| Swagger (swagger-core) | https://github.com/swagger-api/swagger-core |
| Spring Security 7 WebSocket (PR) | https://github.com/spring-projects/spring-security/pull/17328 |
| Spring Security 7 (discussion thread) | https://github.com/spring-projects/spring-security/pull/17328 |
| Spring Boot 4 & Spring Framework 7 (overview) | https://www.baeldung.com/spring-boot-4-spring-framework-7 |