Atualize uma aplicação Java 8 crítica por etapas: inventarie todas as dependências, passe primeiro para o Java 11 e depois para cada versão de suporte de longo prazo seguinte, e deixe que os testes e os testes de carga decidam quando cada passo é seguro. A maior parte do trabalho está nas bibliotecas, nas ferramentas de build e no código que mexe nos elementos internos do JDK, e não na lógica de negócio.
Ficar no Java 8 reduz as opções todos os anos
Uma aplicação Java 8 pode continuar a funcionar durante anos, e é essa a armadilha. As correções de segurança dependem cada vez mais de um contrato de suporte pago ou de uma distribuição que ainda as retroporte, e as principais bibliotecas seguiram em frente. O Spring Boot 3, por exemplo, exige no mínimo o Java 17, pelo que ficar no Java 8 também congela as versões da framework.
As JVM mais recentes também executam melhor o mesmo código. O G1 é o garbage collector por omissão desde o Java 9, coletores de pausa curta como o ZGC estão prontos para produção, e as compact strings reduzem o consumo de memória em cargas com muito texto. Os engenheiros contam também com funcionalidades modernas da linguagem, como records, text blocks e switch expressions, o que torna mais difícil encontrar quem queira trabalhar numa base de código Java 8.
Começar por um inventário de tudo aquilo de que a aplicação depende
A atualização do JDK em si raramente é a parte difícil. A parte difícil é a longa cauda de bibliotecas, plugins e agentes que foram escritos para o Java 8 e nunca atualizados. Construa uma lista completa antes de alterar seja o que for, e registe a versão de cada elemento.
- O fornecedor e a versão do JDK em cada ambiente, dos portáteis dos programadores à produção.
- Dependências diretas e transitivas, exportadas da árvore de dependências do Maven ou do relatório de dependências do Gradle.
- Plugins de build, geradores de código e processadores de anotações como o Lombok.
- O servidor aplicacional ou o contentor de servlets, se a aplicação correr dentro de um.
- Agentes Java de monitorização, profiling ou segurança, que se ligam profundamente à JVM.
- Código que usa APIs internas do JDK, detetado com a ferramenta jdeps e a sua opção jdk-internals.
- Flags da JVM, definições do garbage collector e quaisquer scripts que analisem o resultado de java -version.
Avançar de versão LTS em versão LTS em vez de saltar
Passe do Java 8 para o 11, depois para o 17 e depois para o 21 ou o 25. Cada salto tem o seu próprio conjunto de remoções e de mudanças de comportamento, e um salto único mistura-as todas numa falha impossível de diagnosticar. Corrigir uma camada de cada vez mantém cada passo suficientemente pequeno para ser revisto e revertido.
Sempre que possível, atualize as bibliotecas ainda no Java 8, já que muitas versões recentes suportam tanto os JDK antigos como os novos. Depois, corra o build existente na nova JVM antes de mudar o alvo de compilação. Isto separa os problemas de runtime dos problemas de compilação, e a flag release do compilador mantém explícito o alvo do bytecode.
Fazer da bateria de testes a porta de cada passo
Os testes são o único sinal objetivo de que a aplicação atualizada continua a comportar-se da mesma forma. Se os percursos críticos tiverem pouca cobertura, escreva primeiro testes de caracterização: testes que capturam o que o sistema faz hoje, incluindo os seus casos-limite estranhos, sem julgar se esse comportamento está certo.
Acrescente testes de integração que corram contra uma base de dados real e message brokers reais, porque muitas falhas de atualização só aparecem nessas fronteiras. Em sistemas com muitos cálculos, faça passar as mesmas entradas pela versão antiga e pela nova e compare as saídas campo a campo. Preste atenção às datas, aos números e ao texto: o Java 9 passou a usar por omissão os dados de localização CLDR e o Java 18 tornou o UTF-8 o conjunto de caracteres por omissão, e ambas as mudanças podem alterar em silêncio a formatação ou a leitura dos dados.
Conhecer as armadilhas que partem o código Java 8
A maioria das avarias cai em algumas categorias conhecidas. Procure cada uma antes da atualização, em vez de a descobrir em produção.
- Módulos Java EE e CORBA removidos: o Java 11 retirou do JDK o JAXB, o JAX-WS, o JavaBeans Activation e as common annotations, que têm de ser acrescentados de novo como dependências explícitas.
- Encapsulamento forte dos elementos internos do JDK: o acesso reflexivo ilegal gerava avisos a partir do Java 9, é recusado por omissão desde o Java 16 e não pode ser reativado globalmente desde o Java 17. Corrija-o atualizando a biblioteca; use add-opens apenas como exceção documentada e temporária.
- Ferramentas de bytecode desatualizadas: versões antigas do Lombok, do Mockito, do Byte Buddy, do ASM e de plugins de build falham com versões mais recentes do formato de classe.
- Funcionalidades removidas: o motor JavaScript Nashorn foi removido no Java 15, e o Security Manager foi descontinuado no Java 17 e desativado definitivamente no Java 24.
- Flags da JVM removidas: o garbage collector CMS foi removido no Java 14, e uma JVM iniciada com opções removidas pode recusar-se a arrancar.
Provar o desempenho sob carga realista, e não num portátil
Um novo JDK muda o garbage collector, o compilador JIT e a organização da memória, pelo que o desempenho pode mudar num sentido ou no outro. Corra um teste de carga que reproduza o perfil de tráfego de produção, no mesmo hardware ou tipo de instância, antes e depois de cada passo. Compare os percentis de latência, o débito, a memória e as pausas do garbage collector, e deixe o JIT aquecer antes de medir.
Os nossos engenheiros lideraram a migração de Java 8 para 16 de uma aplicação crítica de trading diário de gás para uma mesa nacional de negociação de gás, através da Sopra Steria. Nesse sistema, os cálculos de capacidade e de fluxos de energia tinham de se manter corretos e rápidos sob carga de alta frequência, pelo que a atualização foi acompanhada de trabalho de otimização desses cálculos e validada sob carga em vez de dada como garantida.
Implementar de forma gradual e manter um caminho de volta
Coloque o runtime atualizado primeiro numa instância ou numa pequena parte do tráfego, e acompanhe as taxas de erro, a latência e o comportamento do garbage collector face à referência Java 8. Mantenha o build anterior pronto a ser reposto até a nova versão ter atravessado os ciclos normais do negócio, incluindo fechos de mês ou períodos de pico.
Só então retire os artefactos Java 8 e comece a usar as novas funcionalidades da linguagem. Se pretende ajuda para planear ou executar uma atualização deste tipo, a SDK Enterprises forma a equipa com engenheiros internos e especialistas selecionados, ao abrigo de um único contrato pelo qual responde.
Pontos essenciais
- O esforço de uma atualização do Java 8 está sobretudo nas dependências, nas ferramentas de build e no uso de elementos internos do JDK, e não na lógica de negócio.
- Avançar de versão LTS em versão LTS, uma de cada vez, para que cada falha tenha uma causa única e identificável.
- Os testes de caracterização e de integração são a porta de cada passo, sobretudo no que toca a datas, números e codificação de texto.
- Validar o desempenho sob uma carga semelhante à de produção, porque as mudanças no garbage collector e no JIT podem alterar os resultados num sentido ou no outro.
- Implementar primeiro numa pequena parte do tráfego e manter o build Java 8 pronto a ser reposto até o novo runtime ter dado provas.
Perguntas frequentes
É possível passar diretamente do Java 8 para o Java 21?
É, mas herdam-se de uma só vez todas as mudanças do Java 9 ao 21, o que torna as falhas difíceis de rastrear. Passar pelo 11 e pelo 17 custa um pouco mais de tempo de build e poupa muita depuração num sistema crítico.
Quanto tempo demora uma atualização do Java 8?
Depende do número de dependências, de quantas usam elementos internos do JDK, da cobertura de testes e dos testes de carga de que o sistema precisa. Um inventário e uma execução de ensaio na nova JVM dão cedo uma estimativa realista, antes de se comprometer com um calendário.
É preciso reescrever código para usar as novas funcionalidades do Java?
Não. O Java mantém uma forte compatibilidade com versões anteriores para o código que se limita a APIs-padrão não descontinuadas. Adote os records, os text blocks e outras funcionalidades de forma gradual, depois de o runtime atualizado estar estável em produção.