Spring Boot 3.5 encerra suporte OSS no dia 30/06/2026. Se o seu projeto ainda está no Boot 3.x, você tem menos de 30 dias para planejar a migração. O Boot 4 exige Java 21, elimina JUnit 4 do classpath padrão, força Hibernate 7 e remove Undertow. Aqui está o roteiro completo com código before/after para cada breaking change.

Nesse artigo você vai encontrar: o checklist dos 5 breaking changes mais impactantes do Spring Boot 4 com código before/after funcional em Java, a ordem recomendada de migração para não quebrar tudo ao mesmo tempo, e os erros mais comuns que times cometem tentando fazer tudo de uma vez.

Por que migrar agora: o prazo real

Spring Boot 3.5 tem suporte OSS até 30/06/2026. Depois disso, sem correções de segurança, sem patches, sem releases. Se seu projeto está em produção no Boot 3.x, a janela de migração planejada é agora, não depois que aparecer o CVE.

Spring Boot 4.1 está em milestone ativo (4.1.0-M4). A versão GA do Boot 4.0 foi lançada em fevereiro de 2026 com a stack completa: Spring Framework 7, Jakarta EE 11, Hibernate 7, JUnit 5 obrigatório e Java 21 como baseline. Não é uma atualização incremental de versão, é uma mudança de plataforma.

Breaking Change 1: Java 21 é obrigatório

Spring Boot 4 não sobe com Java 17 ou 11. Se você tentar, o erro é direto:

Error: LinkageError occurred while loading main class
java.lang.UnsupportedClassVersionError: Unsupported major.minor version 65.0

No pom.xml:

<!-- ANTES (Spring Boot 3.x) -->
<properties>
    <java.version>17</java.version>
</properties>

<!-- DEPOIS (Spring Boot 4) -->
<properties>
    <java.version>21</java.version>
</properties>

Use o Maven Toolchains Plugin para garantir que o projeto compila com Java 21 em todos os ambientes antes de subir o Boot 4:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-toolchains-plugin</artifactId>
    <configuration>
        <toolchains>
            <jdk>
                <version>21</version>
                <vendor>temurin</vendor>
            </jdk>
        </toolchains>
    </configuration>
</plugin>

Breaking Change 2: JUnit 4 foi removido do classpath padrão

Spring Boot 4 exclui junit:junit e o spring-test não mais inclui o runner do JUnit 4. Se você tem testes com @RunWith, eles simplesmente não compilam.

// ANTES (JUnit 4 - nao compila mais no Boot 4)
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class PedidoServiceTest {
    @Test
    public void deveCriarPedidoComSucesso() { }
}

// DEPOIS (JUnit 5 - padrao no Spring Boot 4)
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class PedidoServiceTest {
    @Test
    void deveCriarPedidoComSucesso() { }
}

Mapeamento completo das anotações:

// org.junit.Test             -> org.junit.jupiter.api.Test
// org.junit.Before           -> org.junit.jupiter.api.BeforeEach
// org.junit.After            -> org.junit.jupiter.api.AfterEach
// org.junit.BeforeClass      -> org.junit.jupiter.api.BeforeAll
// org.junit.AfterClass       -> org.junit.jupiter.api.AfterAll
// org.junit.Ignore           -> org.junit.jupiter.api.Disabled
// @RunWith(MockitoJUnitRunner)-> @ExtendWith(MockitoExtension.class)

Use a recipe org.openrewrite.java.testing.junit5.JUnit4to5Migration do OpenRewrite para automatizar a conversão em projetos grandes.

Breaking Change 3: Hibernate 7 e a API de persistência que mudou

Hibernate 7 implementa Jakarta Persistence 3.2 e quebra a API de Session diretamente. Os métodos save(), update(), saveOrUpdate() e delete() foram removidos da interface Session.

// ANTES (Hibernate 6 / Spring Boot 3.x)
public Produto salvar(Produto produto) {
    Session session = entityManager.unwrap(Session.class);
    return (Produto) session.save(produto);  // removido no Hibernate 7
}

public void atualizar(Produto produto) {
    Session session = entityManager.unwrap(Session.class);
    session.update(produto);  // removido no Hibernate 7
}

// DEPOIS (Hibernate 7 / Spring Boot 4)
public Produto salvar(Produto produto) {
    entityManager.persist(produto);
    return produto;
}

public Produto atualizar(Produto produto) {
    return entityManager.merge(produto);
}

public void excluir(Produto produto) {
    entityManager.remove(
        entityManager.contains(produto)
            ? produto
            : entityManager.merge(produto)
    );
}

Quem usou JpaRepository do Spring Data JPA não precisa mexer em quase nada. O problema fica nos repositórios customizados que fazem session.unwrap() diretamente.

Breaking Change 4: Undertow removido e Jakarta EE 11

Undertow foi removido do Spring Boot 4 porque não é compatível com Servlet 6.1 (Jakarta EE 11). Se você usava Undertow como embedded server:

<!-- ANTES (Spring Boot 3.x com Undertow) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

<!-- DEPOIS (Spring Boot 4 - Undertow nao existe mais, usar Tomcat ou Jetty) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <!-- Tomcat e o padrao -->
</dependency>

Breaking Change 5: Spring Security 7 e CSRF habilitado por padrão

Spring Security 7 habilita proteção CSRF por padrão para todos os endpoints, incluindo os REST. Resultado: POST/PUT/DELETE voltam 403 sem nenhuma mudança visível no seu código.

// ANTES (Spring Boot 3.x - sem CSRF em APIs REST era comum)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/**").authenticated()
            .anyRequest().permitAll()
        )
        .httpBasic(Customizer.withDefaults());
    return http.build();
}

// DEPOIS (Spring Boot 4 - precisa ser explicito sobre CSRF em APIs REST)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/**").authenticated()
            .anyRequest().permitAll()
        )
        .csrf(csrf -> csrf
            .ignoringRequestMatchers("/api/**")  // desabilitar CSRF para REST stateless
        )
        .httpBasic(Customizer.withDefaults());
    return http.build();
}

Desabilitar CSRF para APIs REST é válido quando você usa autenticação stateless (JWT, OAuth2 Bearer Token). CSRF protege sessões baseadas em cookie.

A ordem correta de migração

Fase 1: Java 21 primeiro (sem mudar o Boot)
Suba o Java para 21 no projeto ainda no Spring Boot 3.5. Execute todos os testes. Resolva incompatibilidades de API. Só depois avance.

Fase 2: JUnit 5 (ainda no Boot 3.5)
Migre os testes de JUnit 4 para JUnit 5. Use o OpenRewrite para automatizar. Execute tudo. Certinho antes de avançar.

Fase 3: Boot 4 com feature flags
Atualize para Spring Boot 4. Execute os testes. Espere ver erros de Hibernate e CSRF. Corrija um por vez.

Fase 4: Limpeza e otimização
Remova dependências de Undertow. Elimine código legado. Habilite features do Java 21 que o Boot 4 suporta (Records em @ConfigurationProperties, Virtual Threads).

FAQ: Perguntas Frequentes

Posso usar Spring Boot 4 com Java 17?
Não. Java 21 é o baseline obrigatório. O Spring Framework 7 usa APIs introduzidas no Java 21 internamente.

Meu Spring Data JPA vai funcionar sem reescrita?
Na maioria dos casos sim. O problema fica em repositórios customizados que usam Session.save() ou Session.update() diretamente via Hibernate.

O que acontece com minha aplicação no Boot 3.5 depois de 30/06/2026?
A aplicação continua rodando. Sem suporte OSS significa sem novos patches de segurança. O risco real são CVEs novos descobertos depois do EOL ficarem sem fix oficial.

Vale pular o Boot 4.0 e esperar o Boot 4.1?
Boot 4.1 ainda está em milestone (M4). Para produção, Boot 4.0 GA é a versão estável. Começar agora com Boot 4.0 e depois atualizar para 4.1 é mais seguro do que esperar.

OpenRewrite consegue migrar tudo automaticamente?
Não completamente. A recipe cobre bastante coisa, mas não cobre Hibernate 7 (depende do comportamento de negócio) nem as regras de CSRF (decisão de arquitetura de segurança). OpenRewrite é um acelerador, não uma solução completa.

Migração para Spring Boot 4 não é opcional se você quer manter seu projeto com suporte ativo depois de junho. Com a sequência certa não precisa virar um sprint de emergência. Comenta aqui o breaking change que mais te preocupa no seu projeto.

Segue o Meu Universo Nerd para não perder o próximo conteúdo da série Spring Boot 4.