Java 27 - Étude des nouveautés ☕

Ce document liste les nouvelles fonctionnalités de Java 27 ainsi que les fonctionnalités en preview ou en incubation qui évoluent. Cette version ne supprime aucune API. 

1. Introduction 

Cette version contient principalement : 

  • L’activation par défaut des en-têtes d’objets compacts (Compact Object Headers) 
  • Le garbage collector G1 par défaut dans tous les environnements 
  • Un échange de clés résistant aux ordinateurs quantiques pour TLS 1.3 
  • Le masquage des données sensibles dans les enregistrements JDK Flight Recorder 

La sortie est prévue pour le 15 septembre 2026. C’est la deuxième version depuis la dernière LTS, Java 26. 

Une version calme côté langage : aucune nouveauté syntaxique n’est finalisée, et cinq des neuf JEPs sont des fonctionnalités déjà connues qui repartent pour un tour. Les quatre autres, en revanche, agissent directement sur le runtime, et deux d’entre elles s’appliqueront à nos applications sans que nous ayons une seule ligne à écrire. 

2. Les fonctionnalités finalisées 

2.1 JEP 534: Compact Object Headers by Default 

Tout objet Java en mémoire traîne avec lui un en-tête, avant même la moindre donnée utile. Cet en-tête occupe historiquement 96 bits, répartis en deux morceaux : le Mark Word, qui contient le hash code d’identité, l’âge de l’objet et les bits de verrou, et le Class Word, qui pointe vers les métadonnées de la classe. 

Le détail amusant, c’est que sur ces 96 bits, 27 ne servaient à rien. Les en-têtes compacts fusionnent les deux morceaux en une seule valeur de 64 bits, en logeant un pointeur de classe compressé dans l’espace ainsi récupéré. 

La fonctionnalité a mis trois versions à arriver à maturité : expérimentale en Java 24, promue en fonctionnalité de production en Java 25 mais avec activation manuelle, et enfin activée par défaut ici. 

# Java 25 et 26 : il fallait le demander 
java -XX:+UseCompactObjectHeaders -jar application.jar 
 
# Java 27 : c'est le comportement par défaut, on peut encore revenir en arrière 
java -XX:-UseCompactObjectHeaders -jar application.jar 

L’option de retour arrière disparaîtra dans une version future, autant ne pas construire dessus. 

Le gain annoncé est de 10 à 20 % de heap et de 5 à 10 % de débit. Ce sont des chiffres qui parlent surtout aux applications qui créent énormément de petits objets, ce qui décrit assez bien une application e-commerce classique. 

2.2  JEP 536: JFR In-Process Data Redaction 

Le JDK Flight Recorder enregistre, entre autres, les variables d’environnement, les propriétés système et les arguments du programme. C’est précisément par ces trois canaux que l’on passe les tokens d’API et les mots de passe de base de données. 

Autrement dit, un fichier .jfr récupéré au mauvais endroit peut se lire comme un trousseau de clés : 

export ACCESS_TOKEN=mon_token_secret 
java -XX:StartFlightRecording:filename=recording.jfr \ 
     -Djavax.net.ssl.keyStorePassword=mon_mot_de_passe \ 
     -jar application.jar --dbpassword mon_mot_de_passe_bdd 

Java 27 applique une redaction automatique : tout ce qui ressemble à un secret est remplacé par [REDACTED]. La détection s’appuie sur une liste de motifs appliqués aux clés, insensibles à la casse : token, password, secret, credential, api*key, private*key, jwt et quelques autres. Une seconde liste couvre les arguments du programme. 

Ces motifs ratissent large, et c’est volontaire : dans le doute, mieux vaut masquer un peu trop. Une variable TOKEN_REFRESH_INTERVAL, qui ne contient évidemment aucun secret, sera donc masquée elle aussi. Si cela gêne, la configuration s’ajuste : 

# Remplacer la liste par défaut 
java -XX:FlightRecorderOptions:'redact-key=ACCESS_TOKEN;*keyStorePassword' ... 
 
# L'étendre plutôt que la remplacer (préfixe +) 
java -XX:FlightRecorderOptions:'redact-key=+*confidentiel*' ... 
 
# Retrouver l'ancien comportement 
java -XX:FlightRecorderOptions:'redact-key=none,redact-argument:none' ... 

2.3  JEP 527: Post-Quantum Hybrid Key Exchange for TLS 1.3 

Java 24 avait posé les briques cryptographiques résistantes au quantique, avec l’encapsulation de clés et la signature numérique. Java 27 les met enfin là où elles servent tous les jours : dans la négociation TLS. 

On pourrait se demander pourquoi maintenant, alors qu’aucun ordinateur quantique ne casse le chiffrement actuel. La réponse tient dans une expression : "Harvest now, decrypt later". Rien n’empêche un attaquant d’enregistrer aujourd’hui du trafic chiffré et d’attendre patiemment quelques années pour le déchiffrer. Ce qui circule aujourd’hui en chiffrement classique est donc déjà concerné. 

La solution retenue est l’échange de clés hybride : plusieurs méthodes sont combinées, et l’échange tient tant qu’au moins une d’entre elles n’a pas été cassée. Java associe un algorithme ECDHE, classique, à un algorithme ML-KEM, résistant au quantique, en trois variantes de longueurs de clés. 

Côté développeur, il n’y a rien à faire : si le serveur en face sait le parler, Java 27 l’utilise. 

2.4  JEP 523: Make G1 the Default Garbage Collector in All Environments 

G1 est le garbage collector par défaut depuis Java 9 …​ enfin, presque. Une exception subsistait : sur une machine dotée d’un seul CPU ou de moins de 1792 Mo de mémoire physique, la JVM basculait discrètement sur le Serial GC, qui se comportait alors mieux sur ces configurations modestes. 

Cette exception datait un peu. À force d’améliorations, dont la JEP 522 de Java 26 sur la réduction de la synchronisation, G1 atteint aujourd’hui un débit comparable à celui du Serial GC dans ces conditions, tout en gardant ses meilleures latences. 

La règle particulière disparaît donc, et G1 devient le choix par défaut partout, quel que soit le gabarit de la machine. Le Serial GC n’est pas supprimé pour autant et reste disponible avec -XX:+UseSerialGC. 

C’est le changement de cette version le plus susceptible de se voir en production, notamment sur les conteneurs à faible allocation mémoire, où le GC va changer sans que personne n’ait rien demandé. 

3. Les fonctionnalités en preview ou incubation 

Pour rappel, les JEP en preview ou en incubation sont susceptibles d’évoluer, et ne sont donc pas encore finalisées. Pour les activer, le programme doit être compilé avec des options spécifiques (par exemple, --enable-preview). 

Maturité : 
🟢 Proche de la finalisation : Peu ou pas de changements, la fonctionnalité est stabilisée 
🟡 En évolution active : Des changements significatifs sont encore en cours 
🔴 Bloquée : La fonctionnalité est en attente d’un prérequis externe 

3.1  🟢 JEP 531: Lazy Constants (Third Preview) 

Cette JEP est proposée en preview pour la troisième fois, après une première preview en Java 25, où elle s’appelait encore Stable Values, et une deuxième en Java 26. 

Le but initial est de proposer des constantes initialisées à la demande plutôt qu’au chargement de la classe. Jusqu’ici, obtenir ce comportement de façon correcte en environnement concurrent imposait d’écrire soi-même un double-checked locking ou un initialization-on-demand holder idiom, deux exercices où l’erreur se glisse facilement, y compris chez les développeurs expérimentés. 

private final LazyConstant<Validator> validator = 
        LazyConstant.of(this::createValidator); 
 
// createValidator() n'est exécuté qu'au premier get(), et au plus une fois, 
// même si plusieurs threads arrivent en même temps 
public Set<ConstraintViolation<Order>> validate(Order order) { 
    return validator.get().validate(order); 
} 

Cette itération apporte deux changements : 

  • Suppression des méthodes isInitialized() et orElse(), trop bas niveau, qui invitaient à réagir selon l’état d’initialisation alors que c’est justement ce qu’une constante paresseuse n’est pas censée exposer 
  • Ajout de Set.ofLazy(), qui vient compléter List.ofLazy() et Map.ofLazy(). Les trois collections de base ont désormais leur variante paresseuse 

Set<String> candidats = Set.of("dark-mode", "beta-export", "ai-assistant"); 
Set<String> featuresActives = Set.ofLazy(candidats, this::isFeatureEnabled); 
 
// isFeatureEnabled("ai-assistant") n'est appelé qu'ici, et une seule fois 
if (featuresActives.contains("ai-assistant")) { 
    // ... 
} 

3.2  🟢 JEP 532: Primitive Types in Patterns, instanceof, and switch (Fifth Preview) 

Cette JEP est proposée en preview pour la cinquième fois, après une première preview en Java 23. 

Le but initial est d’autoriser les types primitifs dans les patterns, les expressions instanceof et les instructions switch. Aujourd’hui, le pattern matching ne connaît que les types référence, et le switch sur primitifs se limite à byte, short, char et int, avec uniquement des constantes en étiquettes. 

int score = ...; 
switch (score) { 
    case int s when s >= 90 -> IO.println("très bien"); 
    case int s when s >= 75 -> IO.println("bien"); 
    case int s when s >= 60 -> IO.println("assez bien"); 
    default                 -> IO.println("insuffisant"); 
} 

Aucun changement depuis l’itération précédente. La fonctionnalité repart telle quelle, uniquement pour collecter davantage de retours. 

3.3  🟢 JEP 533: Structured Concurrency (Seventh Preview) 

Cette JEP est proposée en preview pour la septième fois, après une incubation initiale en Java 19 et une refonte complète en Java 25. 

Le but initial est d’appliquer à la concurrence ce que la programmation structurée a fait au GOTO : tous les chemins d’exécution ouverts en lançant des tâches concurrentes se rejoignent en un point unique du code, et à ce point on a la garantie qu’aucun thread orphelin ne continue à tourner dans son coin. 

Les changements de cette itération vont tous dans le même sens, celui d’un rapprochement avec les API déjà en place : 

  • Les joiners allSuccessfulOrThrow(), anySuccessfulOrThrow() et awaitAllSuccessfulOrThrow() lèvent maintenant une ExecutionException, c’est-à-dire le même wrapper que Future.get(), au lieu de la FailedException propre à la preview 
  • StructuredTaskScope et Joiner gagnent un troisième paramètre de type R_X, qui représente le type d’exception que join() peut lever. Joiner<T, R> devient donc Joiner<T, R, R_X>, mais le compilateur infère généralement les types tout seul : la différence ne se voit que si l’on écrit ses propres joiners 
  • onTimeout(), introduite en Java 26, est renommée timeout() 
  • Le joiner awaitAll() est supprimé 
  • Une nouvelle surcharge de open() combine la stratégie par défaut et un opérateur de configuration. Il n’est plus nécessaire de repasser le joiner par défaut juste pour donner un nom ou un timeout au scope 

try (var scope = StructuredTaskScope.open( 
        cfg -> cfg.withTimeout(Duration.ofSeconds(2)).withName("checkout"))) { 
    scope.fork(() -> cartService.getCart(userId)); 
    scope.fork(() -> profileService.getProfile(userId)); 
    scope.join(); 
} 

Pour du code écrit en Java 26, la migration reste modeste : dans la plupart des cas, il s’agit de remplacer un catch (FailedException …​) par un catch (ExecutionException …​). 

3.4  🟢 JEP 538: PEM Encodings of Cryptographic Objects (Third Preview) 

Cette JEP est proposée en preview pour la troisième fois, après une première preview en Java 25 et une deuxième en Java 26. 

Le but initial est de proposer une API concise pour encoder et décoder les clés, les certificats et les listes de révocation de certificats au format PEM (Privacy-Enhanced Mail). Jusqu’à présent, lire une clé privée chiffrée demandait de retirer les délimiteurs à la main, de décoder le Base64, puis d’enchaîner EncryptedPrivateKeyInfo, SecretKeyFactory, Cipher et KeyFactory. Une quinzaine de lignes pour une opération qui tient désormais en trois : 

PrivateKey privateKey = PEMDecoder.of() 
        .withDecryption(passphrase.toCharArray()) 
        .decode(pemEncodedKey, PrivateKey.class); 

Cette itération se limite à des ajustements de nommage et de structure : le record PEM devient une classe, et plusieurs classes et méthodes changent de nom. Les usages simples comme celui ci-dessus ne bougent pas. 

3.5.  🔴 JEP 537: Vector API (Twelfth Incubator) 

Cette JEP est proposée en incubation depuis Java 16, pour la 12ème fois. 

Le but initial est de proposer une API pour les opérations vectorielles, exploitant directement les jeux d’instructions SIMD des processeurs modernes (SSE, AVX) plutôt que du code scalaire classique. 

Aucun changement significatif, exactement comme en Java 26. La raison de cette attente interminable est toujours la même : la classe Vector a vocation à devenir une value class, et l’API restera en incubation tant que la JEP 401: Value Classes and Objects du Projet Valhalla ne sera pas passée en preview dans le JDK. 

4. Conclusion 

Java 27 ne fera pas beaucoup parler de lui dans les conférences : rien de neuf côté syntaxe, et cinq JEPs sur neuf qui repassent le plat. Mais c’est une version qui se voit là où on ne l’attend pas, c’est-à-dire au runtime. Les en-têtes d’objets compacts (JEP 534) et G1 partout (JEP 523) modifient le comportement de nos applications au simple changement de JDK, le masquage JFR (JEP 536) ferme une fuite de secrets à laquelle peu de monde pensait, et TLS post-quantique (JEP 527) protège dès aujourd’hui contre un déchiffrement de demain. 

Du côté des preview, la tendance est à la convergence : Structured Concurrency et Lazy Constants s’alignent sur les API existantes et perdent leurs méthodes les plus discutables, ce qui ressemble beaucoup à une dernière ligne droite. Seule l’API Vector reste immobile, toujours suspendue au Projet Valhalla, qui pourrait bien montrer le bout de son nez en Java 28. 

Auteur Aicha Laafia

Concepteur Développeur 

Vous souhaitez échanger avec un expert ?