12 oct. 2026

Java 25 : nouveautés, fonctionnalités et migration depuis Java 21

Développement logiciel

Rania

HELALI

MacBook Pro with images of computer language codes

12 oct. 2026

Java 25 : nouveautés, fonctionnalités et migration depuis Java 21

Développement logiciel

Rania

HELALI

MacBook Pro with images of computer language codes

12 oct. 2026

Java 25 : nouveautés, fonctionnalités et migration depuis Java 21

Développement logiciel

Rania

HELALI

MacBook Pro with images of computer language codes

2025, l’année où Java fête ses 30 ans.
Et au lieu de se laisser pousser la barbe grise et de raconter ses batailles contre les NullPointerException, il débarque avec Java 25, une version LTS flambant neuve, boostée à la modernité.

Oui, Java a désormais l’énergie d’un junior en startup… mais avec la sagesse d’un architecte système qui a vu des serveurs tomber.

En deux mots : un LTS qui envoie

Java 25, c’est 18 JEPs au compteur (Java Enhancement Proposals, les petites fiches techniques des nouveautés).
Sous le capot : plus de fluidité dans le langage, un démarrage plus rapide, moins de blabla pour écrire du code — et une JVM qui continue de s’affûter.

Bref : Java s’allège sans perdre sa carrure.

Mais pas besoin de passer les 18 JEP au peigne fin. Voyons plutôt celles qui vont réellement intéresser les développeurs.

1. Des programmes Java beaucoup plus légers

Pendant longtemps, afficher trois mots en Java nécessitait déjà une classe et le fameux :

public static void main(String[] args)

Avec JEP 512 – Compact Source Files and Instance Main Methods, on peut désormais écrire :

void main() {
    System.out.println("Hello Java 25!");
}

Pas de classe explicite.
Pas de public static void main.

Pour une application Spring Boot, ça ne changera évidemment pas notre quotidien. Mais pour apprendre Java, tester rapidement une API ou écrire un petit programme, c’est beaucoup plus agréable.

Java ne devient pas Python. Il a juste décidé qu’un Hello World n’avait pas besoin d’un comité d’architecture.

2. Le pattern matching s’ouvre aux types primitifs

Le pattern matching est l’un des domaines où Java évolue beaucoup depuis quelques versions.

Avec JEP 507 – Primitive Types in Patterns, instanceof, and switch, encore en preview, les types primitifs peuvent désormais participer au pattern matching.

Par exemple :

float value = 3.14f;

 

switch (value) {

    case 0f -> System.out.println("Zero");

    case float x when x > 0 -> System.out.println("Positive: " + x);

    case float x -> System.out.println("Other: " + x);

}

int, long, float, double, boolean… peuvent ainsi être utilisés dans davantage de patterns et de switch.

Ce n’est pas une révolution en soi. Mais c’est une étape supplémentaire vers un Java plus expressif et avec moins de conversions manuelles.

3. ScopedValue : une alternative moderne à ThreadLocal

Là, ça devient particulièrement intéressant pour les applications backend.

ThreadLocal est pratique lorsqu’on veut transporter un contexte — utilisateur, correlation ID, contexte de sécurité — sans le passer explicitement de méthode en méthode.

Mais sa durée de vie est liée au thread et son utilisation demande une certaine discipline.

private static final ThreadLocal<String> USER =

        new ThreadLocal<>();

 

try {

    USER.set("alice");

    processRequest();

} finally {

    USER.remove();

}

Le remove() est important : avec des threads réutilisés, oublier le nettoyage peut conserver un contexte plus longtemps que prévu.

Java 25 finalise Scoped Values avec la JEP 506 :

private static final ScopedValue<String> USER =

        ScopedValue.newInstance();

 

ScopedValue.where(USER, "alice").run(() -> {

    processRequest();

});

La différence à retenir est assez simple :

ThreadLocal → contexte attaché au thread
ScopedValue → contexte attaché à une portée d’exécution

Une fois le scope terminé, le binding disparaît.

C’est plus prévisible et surtout beaucoup plus cohérent avec les virtual threads introduits dans Java 21.

4. Structured Concurrency : remettre de l’ordre dans le parallèle

Prenons un service qui doit récupérer simultanément un utilisateur et ses commandes.

Avec ExecutorService, plusieurs Future, les exceptions, les annulations et le shutdown du pool, on peut rapidement écrire beaucoup de code pour résoudre un problème assez simple.

La Structured Concurrency, en cinquième preview dans Java 25 avec la JEP 505, propose une approche différente : considérer plusieurs tâches concurrentes liées comme une seule unité de travail.

try (var scope = StructuredTaskScope.<String>open()) {

 

    var userTask = scope.fork(() -> fetchUser());

    var orderTask = scope.fork(() -> fetchOrder());

 

    scope.join();

 

    System.out.println(

        userTask.get() + " - " + orderTask.get()

    );

}

Les sous-tâches vivent dans un même scope. Leur cycle de vie, leurs erreurs et leur annulation deviennent donc beaucoup plus simples à raisonner.

Et c’est surtout l’ensemble qui devient intéressant :

Virtual Threads + Scoped Values + Structured Concurrency

Java construit progressivement un modèle de concurrence beaucoup plus cohérent que l’empilement historique de pools de threads, ThreadLocal, Future et callbacks.

Structured Concurrency reste néanmoins en preview dans Java 25.

Donc on teste, on apprend… mais on évite peut-être de réécrire toute la prod vendredi après-midi.

5. Des constructeurs un peu moins rigides

Petite évolution, mais plutôt bienvenue : JEP 513 – Flexible Constructor Bodies.

Historiquement, super() devait être la première instruction d’un constructeur enfant.

Java 25 permet d’effectuer certains traitements avant cet appel :

class RegisteredUser extends User {

 

    RegisteredUser(String username) {

 

        var normalized =

                username.trim().toLowerCase();

 

        if (normalized.isEmpty()) {

            throw new IllegalArgumentException();

        }

 

        super(normalized);

    }

}

On peut donc valider ou préparer les arguments avant d’appeler le constructeur parent.

Moins de helpers artificiels, moins de contorsions, code plus naturel.

Ce n’est pas spectaculaire. Mais c’est typiquement le genre de petite amélioration qu’on apprécie une fois qu’on l’a.

6. Sous le capot, la JVM continue de s’affûter

Toutes les nouveautés intéressantes ne nécessitent pas de changer notre code.

Compact Object Headers

JEP 519 – Compact Object Headers réduit l’espace utilisé par les métadonnées associées aux objets Java.

Quelques octets économisés par objet semblent anecdotiques.

Quelques octets multipliés par plusieurs millions d’objets, beaucoup moins.

Pour les grosses applications backend, cela peut réduire l’empreinte mémoire et améliorer l’utilisation du cache CPU.

Et le meilleur dans tout ça : aucun changement dans le code métier.

AOT : démarrer plus vite

Java 25 poursuit également le travail autour de l’Ahead-of-Time avec les JEP 514 et 515.

L’objectif est de permettre à la JVM d’exploiter plus tôt des informations de profilage afin de réduire le temps nécessaire avant d’atteindre de bonnes performances.

En pratique : moins de warm-up et de meilleurs temps de démarrage.

Un sujet particulièrement intéressant pour les microservices et les environnements cloud où les instances peuvent être créées régulièrement.


7. JFR devient encore meilleur pour comprendre ce qui se passe en prod

Java Flight Recorder continue également d’évoluer.

Java 25 apporte notamment des améliorations autour :

  • du CPU-Time Profiling ;

  • du sampling ;

  • du Method Timing & Tracing.

L’objectif : comprendre plus précisément quelles méthodes consomment réellement du CPU et ce qui se passe dans la JVM, sans transformer l’application en sapin de Noël de logs.

La prochaine fois qu’un ticket arrive avec :

« L’application est lente depuis hier. »

on aura peut-être mieux à proposer que :

« Tu as essayé de redémarrer le pod ? »


Et le reste ?

Java 25 contient évidemment d’autres évolutions intéressantes.

La Vector API poursuit son développement pour les calculs SIMD et les workloads numériques intensifs.

Côté sécurité, le JDK introduit notamment une API standardisée pour la dérivation de clés et poursuit le travail autour du format PEM.

Generational Shenandoah fait évoluer le garbage collector Shenandoah vers un modèle générationnel.

Java 25 permet également les Module Import Declarations :

import module java.base;

Et le support du x86 32 bits disparaît.

Si votre serveur de production tourne encore en 32 bits, Java 25 n’est probablement pas le sujet le plus urgent de votre backlog.

Ce qu’il faut vraiment retenir

Si je devais résumer Java 25 en quatre points :

  1. Le langage devient moins cérémonieux.
    Compact source files, constructeurs plus flexibles, imports de modules et pattern matching enrichi continuent à réduire le boilerplate historique de Java.

  2. La concurrence moderne prend forme.
    Virtual Threads + Scoped Values + Structured Concurrency commencent à former un ensemble particulièrement cohérent pour les applications concurrentes.

  3. Les performances ne se jouent plus seulement dans notre code.
    AOT, Compact Object Headers et Generational Shenandoah montrent tout le travail effectué directement dans la JVM.

  4. L’observabilité devient toujours plus solide.
    JFR continue de devenir un véritable outil de diagnostic des performances en production.

Faut-il passer de Java 21 à Java 25 ?

Java 21 reste évidemment une excellente LTS.

Mais Java 25 devient une cible naturelle pour les entreprises qui préparent leur prochain cycle de migration.

Pas uniquement parce qu’il s’agit d’une nouvelle LTS.

Mais parce qu’entre Java 21 et Java 25, une direction se confirme : Java cherche moins à révolutionner son langage qu’à enlever progressivement les petites frictions qui se sont accumulées au fil des années.

Moins de boilerplate.
Une concurrence plus simple à raisonner.
Une JVM plus efficace.
De meilleurs outils pour comprendre ce qui se passe en production.

Java a 30 ans.

Il n’essaie plus vraiment de devenir le langage cool de la soirée.

Il fait quelque chose de probablement plus utile : il continue tranquillement à supprimer les raisons qu’on avait de se moquer de lui.