Aggiornate un'applicazione Java 8 critica per tappe: fate l'inventario di ogni dipendenza, passate prima a Java 11, poi a ciascuna delle versioni a supporto a lungo termine (LTS) successive, e lasciate che test e test di carico decidano quando ogni tappa è sicura. La maggior parte del lavoro sta nelle librerie, negli strumenti di build e nel codice che tocca le parti interne del JDK, non nella vostra logica di business.
Restare su Java 8 riduce le vostre opzioni ogni anno
Un'applicazione Java 8 può continuare a girare per anni, ed è proprio questa la trappola. Le correzioni di sicurezza dipendono sempre più da un contratto di supporto a pagamento o da una distribuzione che le riporta ancora sulle versioni vecchie, e le principali librerie sono andate avanti. Spring Boot 3, per esempio, richiede almeno Java 17, quindi restare su Java 8 blocca anche le versioni del vostro framework.
Le JVM più recenti eseguono anche meglio lo stesso codice. G1 è il garbage collector predefinito da Java 9, collector a pause ridotte come ZGC sono pronti per la produzione, e le compact strings riducono l'uso di memoria nei carichi di lavoro ricchi di testo. Gli ingegneri si aspettano inoltre funzionalità moderne del linguaggio come record, text block e switch expression, e questo rende più difficile trovare persone per una base di codice Java 8.
Partite da un inventario di tutto ciò da cui dipende l'applicazione
L'aggiornamento del JDK in sé è raramente la parte difficile. La parte difficile è la lunga coda di librerie, plugin e agenti scritti per Java 8 e mai aggiornati. Stilate un elenco completo prima di cambiare qualsiasi cosa, e annotate la versione di ogni elemento.
- Il fornitore e la versione del JDK in ogni ambiente, dai portatili degli sviluppatori alla produzione.
- Le dipendenze dirette e transitive, esportate dall'albero delle dipendenze di Maven o dal report delle dipendenze di Gradle.
- Plugin di build, generatori di codice e annotation processor come Lombok.
- L'application server o il servlet container, se l'applicazione gira al suo interno.
- Gli agenti Java per monitoraggio, profilazione o sicurezza, che si agganciano in profondità alla JVM.
- Il codice che usa API interne del JDK, individuato con lo strumento jdeps e la sua opzione jdk-internals.
- I flag della JVM, le impostazioni del garbage collector e qualsiasi script che analizzi l'output di java -version.
Passate da una versione LTS all'altra invece di saltare
Passate da Java 8 a 11, poi a 17, poi a 21 o 25. Ogni passaggio ha le sue rimozioni e i suoi cambiamenti di comportamento, e un unico salto li mescola tutti in un solo guasto impossibile da diagnosticare. Correggere un livello alla volta mantiene ogni tappa abbastanza piccola da essere revisionata e annullata.
Dove potete, aggiornate le librerie restando ancora su Java 8, perché molte versioni recenti supportano sia i JDK vecchi sia quelli nuovi. Poi eseguite la build esistente sulla nuova JVM prima di cambiare il target di compilazione. Così separate i problemi di runtime da quelli di compilazione, e il flag release del compilatore mantiene esplicito il target del bytecode.
Fate della suite di test il via libera per ogni tappa
I test sono il vostro unico segnale oggettivo che l'applicazione aggiornata si comporta ancora allo stesso modo. Se i percorsi critici sono poco coperti, scrivete prima dei test di caratterizzazione: test che fissano ciò che il sistema fa oggi, compresi i suoi strani casi limite, senza giudicare se quel comportamento sia corretto.
Aggiungete test di integrazione che girano su un database reale e su message broker reali, perché molti errori di aggiornamento compaiono solo a quei confini. Per i sistemi ricchi di calcoli, fate passare gli stessi input nella versione vecchia e in quella nuova e confrontate gli output campo per campo. Fate attenzione a date, numeri e testo: Java 9 è passato per impostazione predefinita ai dati di localizzazione CLDR e Java 18 ha reso UTF-8 il charset predefinito, ed entrambe le cose possono cambiare in silenzio la formattazione o il parsing.
Conoscete le insidie che rompono il codice Java 8
La maggior parte dei malfunzionamenti rientra in poche categorie note. Cercatele una per una prima dell'aggiornamento invece di scoprirle in produzione.
- Moduli Java EE e CORBA rimossi: Java 11 ha tolto dal JDK JAXB, JAX-WS, JavaBeans Activation e le common annotations, che vanno quindi reintrodotti come dipendenze esplicite.
- Incapsulamento forte delle parti interne del JDK: l'accesso riflessivo illegale produceva avvisi da Java 9, è negato per impostazione predefinita da Java 16 e da Java 17 non si può più riattivare a livello globale. Risolvete aggiornando la libreria; usate add-opens solo come eccezione temporanea e documentata.
- Strumenti di bytecode datati: le versioni vecchie di Lombok, Mockito, Byte Buddy, ASM e dei plugin di build falliscono sulle versioni più recenti dei file class.
- Funzionalità rimosse: il motore JavaScript Nashorn è stato rimosso in Java 15, e il Security Manager è stato deprecato in Java 17 e disattivato definitivamente in Java 24.
- Flag della JVM rimossi: il garbage collector CMS è stato rimosso in Java 14, e una JVM avviata con opzioni rimosse può rifiutarsi di partire.
Dimostrate le prestazioni sotto un carico realistico, non su un portatile
Un nuovo JDK cambia il garbage collector, il compilatore JIT e l'organizzazione della memoria, quindi le prestazioni possono muoversi in entrambe le direzioni. Eseguite un test di carico che riproduca il profilo di traffico della produzione, sullo stesso hardware o sullo stesso tipo di istanza, prima e dopo ogni tappa. Confrontate i percentili di latenza, il throughput, la memoria e le pause del garbage collector, e lasciate che il JIT si scaldi prima di misurare.
I nostri ingegneri, tramite Sopra Steria, hanno guidato la migrazione da Java 8 a Java 16 di un'applicazione critica di day-trading sul gas per un desk nazionale di trading del gas. Su quel sistema i calcoli di capacità e di flussi energetici dovevano restare corretti e veloci sotto un carico ad alta frequenza, quindi l'aggiornamento è andato di pari passo con un lavoro di ottimizzazione di quei calcoli ed è stato validato sotto carico, non dato per scontato.
Rilasciate in modo graduale e tenete una via di ritorno
Rilasciate il runtime aggiornato prima su una sola istanza o su una piccola quota del traffico, e osservate tassi di errore, latenza e comportamento del garbage collector rispetto al riferimento Java 8. Mantenete rilasciabile la build precedente finché la nuova versione non ha attraversato i normali cicli di attività, compresi fine mese o periodi di picco.
Solo allora eliminate gli artefatti Java 8 e cominciate a usare le nuove funzionalità del linguaggio. Se volete aiuto per pianificare o realizzare un aggiornamento di questo tipo, SDK Enterprises lo affida a ingegneri interni e specialisti selezionati, con un unico contratto di cui risponde.
Punti chiave
- Lo sforzo di un aggiornamento da Java 8 sta soprattutto in dipendenze, strumenti di build e uso delle parti interne del JDK, non nella logica di business.
- Passate da una versione LTS alla successiva una alla volta, così ogni errore ha una causa unica e rintracciabile.
- I test di caratterizzazione e di integrazione sono il via libera per ogni tappa, soprattutto per date, numeri e codifica del testo.
- Validate le prestazioni sotto un carico simile a quello di produzione, perché i cambiamenti di garbage collector e JIT possono spostare i risultati in entrambe le direzioni.
- Rilasciate prima su una piccola quota del traffico e mantenete rilasciabile la build Java 8 finché il nuovo runtime non ha dato prova di sé.
Domande frequenti
Si può passare direttamente da Java 8 a Java 21?
Si può, ma ereditate in un colpo solo tutti i cambiamenti da Java 9 a 21, e questo rende gli errori difficili da rintracciare. Passare per 11 e 17 costa un po' più di tempo di build e fa risparmiare molto debugging su un sistema critico.
Quanto tempo richiede un aggiornamento da Java 8?
Dipende dal numero di dipendenze, da quante di queste usano parti interne del JDK, dalla copertura dei test e da quanti test di carico servono al sistema. Un inventario e una prova sulla nuova JVM vi danno presto una stima realistica, prima di impegnarvi su una tempistica.
Bisogna riscrivere il codice per usare le nuove funzionalità di Java?
No. Java mantiene una forte retrocompatibilità per il codice che si limita alle API standard e non deprecate. Adottate record, text block e le altre funzionalità in modo graduale, dopo che il runtime aggiornato è stabile in produzione.