Dia 30 de junho de 2026 o Spring Boot 3.5 perde o suporte OSS. Depois dessa data, sem patch de segurança, sem correção de CVE, sem rede. E olha que a fila de CVE do Spring em 2026 foi a maior da história: só no patch day de 08/06 saíram 18 correções de uma vez. Quem ficar para trás vai rodar produção com buraco conhecido e sem remendo oficial.
O problema é que o caminho até o Spring Boot 4 não é um bump de versão tranquilo. São mais de 50 mudanças que quebram código de produção: Jackson 3 com group ID novo, Spring Security 7 obrigando o Lambda DSL, JSpecify ligando null-safety e o Spring AI 1.x simplesmente parando de funcionar. Nesse artigo eu te dou o checklist real, com código antes e depois, para você migrar sem descobrir cada armadilha na marra, às 2 da manhã de um deploy.
O relógio do Boot 3.5 está zerando (e o que EOL significa de verdade)
EOL (end of life) não é um aviso burocrático. Na prática, é o dia em que o time do Spring para de olhar para aquela linha de versão. Saiu um CVE crítico em julho? No Boot 3.5 ele não vai ser corrigido no ramo OSS. Você teria que pagar suporte estendido (Tanzu/Broadcom) ou subir de versão correndo, justamente o que você queria evitar.
Pense num prédio comercial cujo corpo de bombeiros credenciado encerra o contrato numa data marcada. O prédio continua de pé, as luzes acendem, o elevador funciona. Só que no dia que pegar fogo, ninguém oficial vem apagar. É exatamente esse o risco de manter um serviço em produção rodando uma versão fora de suporte: tudo parece normal até o incidente que não tem quem resolva.
E o contexto de 2026 deixou isso mais pesado. A nota oficial do Spring Framework 7.0.8 e 6.2.19, publicada em 08/06, corrigiu 18 CVEs num único release e avisou que o 6.2.19 é provavelmente o último lançamento OSS da geração 6.2.x, a base do Boot 3.5. Some a isso a onda histórica de relatórios de segurança que o próprio time documentou no post Spring and Security in the Times of AI, e fica claro por que essa migração virou prioridade de calendário, não de backlog.
A boa notícia: a maior parte do trabalho é mecânica e previsível, desde que você saiba onde estão as minas. Bora passar por elas, da que mais derruba produção para a que mais aparece em entrevista.
O que muda de verdade do 3.5 para o 4
Spring Boot 4 sobe junto com o Spring Framework 7 e traz um pacote de mudanças de base. O baseline passa a ser Java 17, a stack adota Jakarta EE 11, e três dependências centrais mudam de comportamento ao mesmo tempo: Jackson, Spring Security e a checagem de nulos com JSpecify. No pom.xml, o ponto de partida é trocar o parent e, se você usa IA, o módulo do Spring AI:
<!-- ANTES (Spring Boot 3.5) -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.6</version>
</parent>
<!-- DEPOIS (Spring Boot 4.0) -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.0</version>
</parent>
Parece um upgrade comum, e é aí que mora a pegadinha. O mvn compile pode até passar. O que quebra vem depois, em runtime, em pontos que o compilador não vê. A fonte oficial de cada item está no Spring Boot 4.0 Migration Guide, mantido no wiki do projeto, e vale deixar ele aberto numa aba enquanto você migra. As próximas seções cobrem as três quebras que mais consomem tempo de quem já tentou.
Jackson 3: a quebra silenciosa que custa horas
Essa é a campeã de tempo perdido. O Spring Boot 4 adota o Jackson 3, e o Jackson 3 mudou de endereço: o group ID saiu de com.fasterxml.jackson para tools.jackson. Se você declara o Jackson manualmente em algum lugar, ou depende de um módulo extra (JSR-310, Kotlin, Afterburner), a dependência some sem aviso claro:
<!-- ANTES: Jackson 2.x -->
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<!-- DEPOIS: Jackson 3.x -->
<dependency>
<groupId>tools.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
E tem o lado das anotações. Se você escreveu serializadores customizados (e quem tem sistema de verdade quase sempre tem), a anotação do Spring mudou de nome. O @JsonComponent virou @JacksonComponent, e o @JsonMixin virou @JacksonMixin:
// ANTES (Spring Boot 3.5 + Jackson 2)
import org.springframework.boot.jackson.JsonComponent;
@JsonComponent
public class DinheiroSerializer extends JsonSerializer<BigDecimal> {
@Override
public void serialize(BigDecimal valor, JsonGenerator gen,
SerializerProvider sp) throws IOException {
gen.writeString(valor.setScale(2).toPlainString());
}
}
// DEPOIS (Spring Boot 4 + Jackson 3)
import org.springframework.boot.jackson.JacksonComponent;
@JacksonComponent
public class DinheiroSerializer extends ValueSerializer<BigDecimal> {
@Override
public void serialize(BigDecimal valor, JsonGenerator gen,
SerializationContext ctx) {
gen.writeString(valor.setScale(2).toPlainString());
}
}
Repare que não mudou só o nome da anotação. A classe base virou ValueSerializer no lugar de JsonSerializer, e a assinatura do método perdeu o throws IOException porque o Jackson 3 trabalha com exceções não checadas. O contexto também trocou de SerializerProvider para SerializationContext. São detalhes que o compilador acusa, então esses são os bons. O perigoso é o comportamento padrão do Jackson 3, que ficou mais estrito.
Em Kotlin, por exemplo, um parâmetro não-nulo sem valor default que chega como null no JSON agora estoura MismatchedInputException, onde no Jackson 2 muitas vezes passava batido. Escreva um teste que prove isso antes de subir:
@Test
void jsonSemCampoObrigatorio_deveFalharNoJackson3() {
String json = "{ \"nome\": \"Jorge\" }"; // faltou "idade"
// No Boot 4 isso lanca MismatchedInputException, nao cria objeto com null
assertThrows(MismatchedInputException.class, () ->
objectMapper.readValue(json, Usuario.class));
}
Como Tech Leader, já vi um deploy de sexta virar plantão de madrugada exatamente por causa de uma mudança de serializer que ninguém tinha mapeado: o JSON de resposta de um endpoint de pagamento mudou o formato de um BigDecimal e um parceiro downstream quebrou silenciosamente. A lição que ficou foi simples e vale aqui: contrato de serialização é parte do contrato da API. Em migração de Jackson, teste o JSON de saída, não só se a aplicação sobe.
Spring Security 7: o Lambda DSL agora é lei
O Spring Security 7 removeu de vez o authorizeRequests(), o and() encadeado, o AntPathRequestMatcher e o MvcRequestMatcher. O Lambda DSL deixou de ser recomendação e virou o único jeito. Quem ainda tem configuração no estilo antigo precisa reescrever:
// ANTES (Spring Security 5/6, estilo encadeado)
@Bean
public SecurityFilterChain filtros(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll()
.and()
.httpBasic();
return http.build();
}
// DEPOIS (Spring Security 7, Lambda DSL obrigatorio)
@Bean
public SecurityFilterChain filtros(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/api/**").authenticated()
.anyRequest().permitAll())
.httpBasic(Customizer.withDefaults())
.build();
}
Aqui está a armadilha que dá medo: a migração mecânica de authorizeRequests() para authorizeHttpRequests() pode mudar o comportamento de autorização sem você perceber. Os dois usam algoritmos de match de path diferentes. O antigo AntPathRequestMatcher e o novo PathPatternRequestMatcher tratam rotas com barra final, parâmetros e wildcards aninhados de formas sutilmente distintas. Uma regra que protegia /api/contas pode deixar de casar com /api/contas/ dependendo do padrão.
O build passa, os testes que você já tinha passam, o PR é aprovado. E uma rota que devia exigir login fica aberta. Por isso, em migração de Security, a regra de ouro é: escreva testes de autorização explícitos para as rotas sensíveis, batendo o status 401/403 esperado, antes de tocar na config. Esse é o tipo de teste que vale ouro num pull request de migração:
@Test
void rotaAdminSemLogin_deveRetornar401() throws Exception {
mockMvc.perform(get("/admin/usuarios"))
.andExpect(status().isUnauthorized());
}
JSpecify: o aviso de nulo que vira erro de build
O Spring Framework 7 adotou o JSpecify como padrão de anotações de nulidade em toda a API. Sozinho isso não quebra nada, a API só passou a declarar com precisão o que pode ou não ser nulo. O ponto de atenção é se você liga uma ferramenta de verificação estática como o NullAway no build. Aí aquele null que você passava para um parâmetro agora marcado como não-nulo deixa de ser aviso e passa a falhar a compilação:
import org.jspecify.annotations.NullMarked;
@NullMarked // tudo neste pacote e nao-nulo por padrao
package com.meuuniversonerd.pagamentos;
Não precisa abraçar tudo de uma vez. A estratégia sadia o risco: migre primeiro, deixe o NullAway em modo de aviso, estabilize a aplicação no Boot 4 e só depois aperte para erro, pacote por pacote. Tentar resolver null-safety no mesmo PR da migração é receita para um diff gigante que ninguém revisa direito.
O checklist de migração, na ordem que funciona
Junta tudo e a sequência que minimiza retrabalho é esta. Faça num branch separado e valide cada passo antes de ir ao próximo:
- 1. Garanta Java 17 ou superior no build e na imagem de runtime antes de qualquer coisa
- 2. Suba o parent para 4.0.0 e rode o build só para ver o que nem compila
- 3. Ajuste o Jackson: group ID
tools.jackson, anotações@JacksonComponent, e teste o JSON de saída dos endpoints críticos - 4. Reescreva a config de Security para Lambda DSL e adicione testes de autorização nas rotas sensíveis
- 5. Confira o Spring AI: se você usa, suba para a linha 2.0, porque a 1.x não roda no Boot 4
- 6. Trate JSpecify em modo de aviso primeiro, erro depois
- 7. Rode a suíte completa e um teste de carga curto em homologação antes de promover
Se você quer revisar antes o pano de fundo de versões e plataforma que cerca essa migração, vale relembrar o que mudou nas novidades recentes do ecossistema Java, como o JDK 27 reduz o consumo de memória da JVM e os cuidados de segurança na onda de CVEs do Spring em 2026, porque os três assuntos se cruzam na hora de planejar o upgrade.
A pergunta de entrevista que essa migração responde
No mercado de hoje, "o que muda do Spring Boot 3 para o 4" já é pergunta de triagem em vaga sênior. E ela separa rápido quem migrou de verdade de quem só leu o changelog. Quem responde "atualizei o parent e funcionou" mostra que nunca encarou a parte difícil. Quem responde "o cuidado real está no Jackson 3 com o group ID novo e na serialização mais estrita, e na config de Security, onde trocar authorizeRequests por authorizeHttpRequests pode mudar a autorização sem o teste acusar" mostra que sentiu a dor na mão.
Essa é a diferença que pega bem numa conversa técnica: você não fala da feature no abstrato, você fala do que quebra, por que quebra e como você prova que consertou. Saber que existe é o mínimo. Saber medir, testar e migrar com segurança é o que coloca seu nome na lista curta.
Takeaways e próximo passo
- Boot 3.5 perde suporte OSS em 30/06/2026: rodar fora de suporte é correr risco de CVE sem patch oficial
- As três minas de produção são Jackson 3, Security 7 e JSpecify: o build passar não significa que está migrado
- Teste o que o compilador não vê: JSON de saída e autorização de rota são onde a migração silenciosamente erra
Sua empresa já marcou a migração para o Boot 4 ou ainda está empurrando com a barriga? Conta nos comentários qual dessas três minas pegou vocês primeiro, quero comparar as histórias de plantão com a galera.
Na próxima semana eu pego a mais perigosa delas e abro o capô: como o authorizeHttpRequests() do Security 7 pode deixar uma rota aberta sem ninguém ver, com um teste que prova a brecha antes do deploy.
Perguntas frequentes (FAQ)
Posso pular do Boot 3.5 direto para o 4 ou preciso passar por versões intermediárias?
Você pode ir direto, desde que esteja no 3.5.x mais recente antes de subir. O time do Spring recomenda chegar ao último patch da 3.5 primeiro, porque ele já traz boa parte das depreciações sinalizadas, o que reduz a quantidade de surpresa no salto para o 4.
Meu serviço compila no Boot 4. Posso considerar a migração concluída?
Não. As quebras mais caras do Boot 4 são de runtime, não de compilação. Serialização do Jackson 3 e comportamento de autorização do Security 7 passam pelo compilador e falham em produção. Migração só está pronta depois da suíte de testes verde mais validação em homologação com tráfego representativo.
Uso Spring AI no projeto. O que acontece na migração?
O Spring AI 1.x não é compatível com o Boot 4. Você precisa subir para a linha 2.0 do Spring AI junto com a migração. Trate isso como item bloqueante do checklist, porque sem ele a aplicação nem inicia.
E se eu não conseguir migrar até 30 de junho?
Duas saídas: contratar suporte estendido comercial (Tanzu/Broadcom) para continuar recebendo patches de segurança no 3.5, ou priorizar a migração e, no intervalo, mitigar exposição (WAF, restrição de rede) nas superfícies mais críticas. O que não dá é seguir em produção fora de suporte fingindo que o risco não existe.
Jackson 3 muda o formato do meu JSON de resposta?
Pode mudar, sim, em casos de datas, BigDecimal e campos nulos, porque os defaults ficaram mais estritos. Por isso a recomendação é testar o JSON de saída dos endpoints que outros sistemas consomem. Contrato de serialização é contrato de API: trate uma mudança aí como breaking change para os consumidores.