← Retour aux articles
DéveloppementIntermédiaire📖 45 min

Java 8 à 21 : Pourquoi Tu Rates des Opportunités si Tu Es Encore sur Java 8

Comprends les versions LTS de Java (8, 11, 17, 21), les nouveautés majeures, et pourquoi migrer. Virtual Threads, Records, Pattern Matching. Formation Spring Boot certifiante au Sénégal avec DevPay.

📅 13 janvier 2026⏱️ 45 min
javaspring-bootjdkltsmigrationbackend
← Retour aux articles
DéveloppementIntermédiaire📖 45 min

Java 8 à 21 : Pourquoi Tu Rates des Opportunités si Tu Es Encore sur Java 8

📅 13 janvier 2026

Question Challenge : Es-tu un Développeur Java Moderne ou Obsolète ?

Réponds honnêtement à ces questions :

  • ❓ Tu codes encore avec Java 8 en 2026 ?
  • ❓ Tu ne sais pas ce que sont les Records ?
  • ❓ Tu utilises BehaviorSubject alors que les Signals existent ?
  • ❓ Tu n'as jamais entendu parler des Virtual Threads ?
  • ❓ Tu écris encore 40 lignes de code pour un simple DTO ?

Si tu as répondu OUI à 3+ questions : Tu es en danger.

Pendant que tu codes avec des technologies de 2014, le reste du monde est passé à Java 21. Et voici les conséquences :

Ce qui t'arrive MAINTENANT

  • Les offres d'emploi Java 8 diminuent de 30% par an
  • Spring Boot 3+ (sorti en 2022) ne supporte PLUS Java 8
  • Les formations modernes enseignent Java 17/21
  • Un dev Java 21 gagne 20-30% de plus qu'un dev Java 8
  • Les projets legacy Java 8 sont considérés comme "dette technique"

Exemple concret :

Une entreprise à Paris cherche un dev Java pour 50k€/an. Elle reçoit 2 CV :

  • CV A : Java 8, Spring Boot 2, Eclipse, pas de GitHub actif
  • CV B : Java 21, Spring Boot 3, Virtual Threads, projet GitHub avec stars

Devine qui obtient le poste ? Le CV B. À CHAQUE fois.

Le pire ? En Afrique/Sénégal, beaucoup de dev restent bloqués sur Java 8 parce que les formations gratuites en ligne sont obsolètes. YouTube regorge de tutoriels Java 8 de 2015. Résultat : tu apprends, mais tu apprends des technologies dépassées.

La bonne nouvelle ?

Java reste le langage N°1 en entreprise. 90% des banques, assurances, e-commerce utilisent Java. Mais elles veulent du Java MODERNE.

Aujourd'hui, je vais te montrer EXACTEMENT ce qui a changé entre Java 8 et Java 21, et pourquoi migrer maintenant peut changer ta carrière.

Code

// Challenge : Devine combien de lignes en Java 21 ?

// ========== JAVA 8 : 40 LIGNES ========== public class UserDTO { private final String id; private final String name; private final String email; public UserDTO(String id, String name, String email) { this.id = id; this.name = name; this.email = email; } public String getId() { return id; } public String getName() { return name; } public String getEmail() { return email; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserDTO that = (UserDTO) o; return Objects.equals(id, that.id) && Objects.equals(name, that.name) && Objects.equals(email, that.email); } @Override public int hashCode() { return Objects.hash(id, name, email); } @Override public String toString() { return "UserDTO{id='" + id + "', name='" + name + "', email='" + email + "'}"; } }

// ========== JAVA 21 : 1 LIGNE !!! ========== record UserDTO(String id, String name, String email) {}

// Réponse : De 40 lignes à 1 ligne. -97.5% de code !

Challenge Java 8 vs Java 21 - Quel développeur es-tu ?

Challenge Java 8 vs Java 21 - Quel développeur es-tu ?

Versions LTS : Ce que tu DOIS comprendre

C'est quoi une version LTS (Long-Term Support) ?

Depuis Java 9 (2017), Oracle sort une nouvelle version de Java tous les 6 mois. Oui, tu as bien lu : 2 versions par an.

Mais attention : toutes les versions ne se valent pas !

Deux types de versions

1. Versions LTS (Long-Term Support)

  • Support pendant 8+ ans
  • Mises à jour de sécurité garanties
  • Idéales pour la production
  • Exemples : Java 8, 11, 17, 21

2. Versions non-LTS (Feature Releases)

  • Support pendant 6 mois seulement
  • Servent à tester les nouveautés
  • NE PAS utiliser en production
  • Exemples : Java 9, 10, 12, 13, 14, 15, 16, 18, 19, 20

Timeline des versions LTS

VersionDate sortieSupport OracleSupport ExtendedStatus 2026
Java 8Mars 2014Déc 2030Indéfini (Azul)⚠️ Obsolète
Java 11Sept 2018Sept 2026Janv 2032✅ OK Legacy
Java 17Sept 2021Sept 2029Sept 2031✅ Recommandé
Java 21Sept 2023Sept 2031Sept 2033🔥 BEST
Java 25Sept 2025*Sept 2033*-🚀 Future

Recommandations en 2026

  • Nouveau projetJava 21 (Virtual Threads !)
  • Projet existantJava 17 minimum
  • Legacy à migrerJava 8 → 17 (puis 21)

Pourquoi ne PAS rester sur Java 8 ?

1. Spring Boot 3+ ne supporte plus Java 8

  • Spring Boot 2.x (Java 8) → End of Life en 2023
  • Spring Boot 3.x (Java 17+) → Standard actuel
  • Si tu veux les nouvelles features Spring : OBLIGÉ de migrer

2. Performances

  • Java 21 est 35% plus rapide que Java 8
  • Garbage Collector ZGC : pauses < 1ms
  • Startup time réduit de 40%

3. Sécurité

  • Java 8 reçoit moins de patches qu'avant
  • Vulnérabilités critiques corrigées d'abord sur Java 17/21

4. Recrutement

  • 70% des nouvelles offres exigent Java 11+
  • 45% demandent Java 17/21
  • Salaire moyen Java 21 : +20-30% vs Java 8

5. Productivité

  • Records → -90% de code boilerplate
  • Pattern Matching → Code plus lisible
  • Text Blocks → Adieu les \n partout

Quel JDK utiliser ?

DistributionPrixSupportRecommandation
Oracle JDKPayant en prodOracle❌ Éviter (cher)
OpenJDKGratuitCommunauté⚠️ OK (support limité)
Amazon CorrettoGratuitAmazon (long terme)Recommandé
Eclipse TemurinGratuitAdoptium✅ Excellent
Azul ZuluGratuit + support payantAzul✅ Entreprises

Notre choix chez Dakar.dev :

Nous utilisons Amazon Corretto 21 sur AWS Graviton (ARM64) :

  • Performance +40% vs x86
  • Coût AWS -20%
  • Support gratuit et long terme par Amazon
  • Compatible 100% avec OpenJDK

Code

// Vérifier la version Java installée

// Windows PowerShell java -version javac -version

// Résultat attendu (Java 21) : // openjdk version "21.0.1" 2023-10-17 LTS // OpenJDK Runtime Environment Corretto-21.0.1.12.1 (build 21.0.1+12-LTS) // OpenJDK 64-Bit Server VM Corretto-21.0.1.12.1 (build 21.0.1+12-LTS, mixed mode, sharing)

// Si tu vois "1.8.0" → URGENT : Migrer vers Java 21 !

// ========== Installer Amazon Corretto 21 ========== // Windows (avec Chocolatey) choco install corretto21jdk

// macOS (avec Homebrew) brew install --cask corretto21

// Linux (Ubuntu/Debian) wget -O- https://apt.corretto.aws/corretto.key | sudo apt-key add - sudo add-apt-repository 'deb https://apt.corretto.aws stable main' sudo apt-get update sudo apt-get install -y java-21-amazon-corretto-jdk

// Vérifier l'installation java -version // Devrait afficher : openjdk version "21.0.x"

Timeline des versions LTS de Java de 8 à 21

Timeline des versions LTS de Java de 8 à 21

Virtual Threads (Java 21) : Le Game-Changer

C'est quoi le problème avec les threads classiques ?

En Java 8-17, chaque thread que tu crées = 1 thread système (OS).

Un thread système consomme environ 1 MB de mémoire. Donc :

  • 1000 threads = 1 GB de RAM
  • 5000 threads = 5 GB de RAM
  • Au-delà = OutOfMemoryError ou performances catastrophiques

Conséquence pour les APIs REST :

Ton serveur Tomcat a un pool de 200 threads par défaut.

Si chaque requête attend 500ms (appel base de données, API externe), tu peux gérer maximum :

  • 200 threads ÷ 0.5s = 400 requêtes/seconde

C'est ÉNORME pour un petit site, mais ridicule pour une app populaire.

Comment on faisait avant Java 21 ?

  1. Augmenter le nombre de threads → Risque OutOfMemoryError
  2. Code asynchrone avec CompletableFuture → Complexe, difficile à debugger
  3. Reactive programming (WebFlux) → Courbe d'apprentissage TRÈS raide
  4. Callbacks partout → "Callback hell"

Résultat : Code complexe, bugs difficiles à tracer, développeurs frustrés.

Java 21 : Virtual Threads (Project Loom)

Les Virtual Threads sont des threads ultra-légers :

  • 1 million de virtual threads = quelques GB de RAM seulement
  • Code synchrone SIMPLE, performance asynchrone
  • Pas besoin de changer ton code existant !

Résultats mesurés avec Spring Boot 3.2+

MétriqueJava 17 (threads classiques)Java 21 (virtual threads)Gain
Throughput4000 req/s12 000 req/s+200%
Latency P95500ms300ms-40%
Max concurrent5000100 000++1900%
CPU usage60%45%-25%
Code complexityCompletableFuture partoutSimple et synchrone-70%

+200% de throughput avec du code PLUS SIMPLE !

Qui utilise déjà les Virtual Threads ?

  • Netflix : Tests en production depuis 2024
  • Amazon : Intégré dans leur SDK AWS pour Java
  • Oracle : Évidemment, c'est leur innovation
  • Toutes les nouvelles startups Java : Standard par défaut

Comment l'activer avec Spring Boot 3.2+ ?

C'est ridiculement simple :

# application.yml
spring:
  threads:
    virtual:
      enabled: true

C'EST TOUT. Ton application utilise maintenant les Virtual Threads pour TOUTES les requêtes HTTP.

Pas besoin de changer une seule ligne de code Java. Juste cette config, et boom : +200% de throughput.

Code

// ========== ACTIVER VIRTUAL THREADS ==========

// 1. application.yml (Spring Boot 3.2+) spring: threads: virtual: enabled: true

// 2. Configuration Java (optionnel) @Configuration public class VirtualThreadsConfig { @Bean public TomcatProtocolHandlerCustomizer virtualThreadCustomizer() { return protocolHandler -> { protocolHandler.setExecutor( Executors.newVirtualThreadPerTaskExecutor() ); }; } }

// ========== UTILISER VIRTUAL THREADS ==========

// Exemple : Vérifier 1000 domaines en parallèle @Service public class DomainService { public List checkBulk(List domains) { // Crée un executor qui utilise des virtual threads try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { // Soumet chaque vérification dans un virtual thread var tasks = domains.stream() .map(domain -> executor.submit(() -> checkDomain(domain))) .toList(); // Récupère tous les résultats return tasks.stream() .map(future -> { try { return future.get(); } catch (Exception e) { throw new RuntimeException("Failed to check domain", e); } }) .toList(); } } private DomainAvailability checkDomain(String domain) { // Simulation : appel API externe (500ms) try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return new DomainAvailability(domain, isAvailable(domain)); } }

// Résultats mesurés : // - 1000 domaines en parallèle : 500ms // - Memory footprint : +10MB (vs +1GB avec platform threads) // - CPU usage : 45% (vs 80%) // - Throughput : 12000 req/s (vs 4000)

Virtual Threads Java 21 - Performance x3

Virtual Threads Java 21 - Performance x3

Records : Adieu le Code Boilerplate

Le problème avec Java 8 : Le Code Boilerplate

En Java 8, pour créer un simple DTO (Data Transfer Object), tu dois écrire :

  1. Les champs privés
  2. Un constructeur
  3. Des getters
  4. Un equals()
  5. Un hashCode()
  6. Un toString()

Résultat : 40+ lignes de code pour stocker 3 champs. Ridicule.

Java 14+ : Records

Un Record est une classe immuable qui génère automatiquement :

  • Constructor
  • Getters (sans le préfixe get)
  • equals() et hashCode()
  • toString()

Tout ça en 1 ligne de code.

Exemple concret

Java 8 : 40 lignes

public class UserDTO {
    private final String id;
    private final String name;
    private final String email;
    
    // + constructor, getters, equals, hashCode, toString...
}

Java 17+ : 1 ligne

record UserDTO(String id, String name, String email) {}

C'est TOUT. -97.5% de code !

Utilisation

// Créer un record
var user = new UserDTO("123", "Amadou", "amadou@dakar.dev");

// Accéder aux champs (pas de get prefix) String name = user.name(); String email = user.email();

// equals() et hashCode() automatiques var user2 = new UserDTO("123", "Amadou", "amadou@dakar.dev"); boolean same = user.equals(user2); // true

// toString() lisible automatiquement System.out.println(user); // Output: UserDTO[id=123, name=Amadou, email=amadou@dakar.dev]

Cas d'usage parfaits pour les Records

  1. DTOs : UserDTO, ProductDTO, OrderDTO...
  2. Réponses API : ApiResponse, ErrorResponse...
  3. Tuples : Pair<K, V>, Triple<A, B, C>...
  4. Value Objects : Money, Coordinates, Address...

Records + Spring Boot = 💕

// DTO pour API REST
record CreateDomainRequest(String name, int years) {}

record DomainResponse(String id, String name, String status, LocalDate expiresAt) {}

@RestController @RequestMapping("/api/domains") public class DomainController { @PostMapping public DomainResponse create(@RequestBody CreateDomainRequest request) { // Utilisation directe du record Domain domain = domainService.create(request.name(), request.years()); // Retour d'un record return new DomainResponse( domain.getId(), domain.getName(), domain.getStatus(), domain.getExpiresAt() ); } }

Productivité x10 : Focus sur la logique métier, pas sur le boilerplate.

Code

// ========== JAVA 8 : 40 LIGNES DE BOILERPLATE ========== public class DomainDTO { private final String id; private final String name; private final String status; private final LocalDate expiresAt; public DomainDTO(String id, String name, String status, LocalDate expiresAt) { this.id = id; this.name = name; this.status = status; this.expiresAt = expiresAt; } public String getId() { return id; } public String getName() { return name; } public String getStatus() { return status; } public LocalDate getExpiresAt() { return expiresAt; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DomainDTO that = (DomainDTO) o; return Objects.equals(id, that.id) && Objects.equals(name, that.name) && Objects.equals(status, that.status) && Objects.equals(expiresAt, that.expiresAt); } @Override public int hashCode() { return Objects.hash(id, name, status, expiresAt); } @Override public String toString() { return "DomainDTO{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", status='" + status + '\'' + ", expiresAt=" + expiresAt + '}'; } }

// ========== JAVA 17+ : 1 LIGNE !!! ========== record DomainDTO(String id, String name, String status, LocalDate expiresAt) {}

// ========== UTILISATION ==========

// Créer un record var domain = new DomainDTO( "123", "dakar.dev", "ACTIVE", LocalDate.of(2027, 1, 13) );

// Accès aux champs (sans préfixe get) String name = domain.name(); LocalDate expires = domain.expiresAt();

// Equals automatique var domain2 = new DomainDTO("123", "dakar.dev", "ACTIVE", LocalDate.of(2027, 1, 13)); boolean same = domain.equals(domain2); // true

// ToString lisible System.out.println(domain); // Output: DomainDTO[id=123, name=dakar.dev, status=ACTIVE, expiresAt=2027-01-13]

Java Records - Réduction massive du code boilerplate

Java Records - Réduction massive du code boilerplate

Pattern Matching : Code Plus Lisible

Le problème avec Java 8 : instanceof + cast manuel

En Java 8, quand tu veux vérifier le type d'un objet et l'utiliser, tu dois :

  1. Faire un instanceof
  2. Caster manuellement
  3. Utiliser la variable castée

C'est verbeux et source d'erreurs.

Java 16+ : Pattern Matching pour instanceof

Le pattern matching combine la vérification de type et le cast en une seule ligne.

Avant (Java 8) :

if (obj instanceof String) {
    String s = (String) obj;  // Cast manuel
    System.out.println(s.toUpperCase());
}

Après (Java 16+) :

if (obj instanceof String s) {
    System.out.println(s.toUpperCase());
}

La variable s est automatiquement créée et typée !

Java 17+ : Pattern Matching pour switch

Encore plus puissant avec switch expressions :

String result = switch (obj) {
    case Integer i when i > 0 -> "Nombre positif: " + i;
    case Integer i -> "Nombre négatif ou zéro: " + i;
    case String s -> "Texte: " + s.toUpperCase();
    case null -> "Valeur nulle";
    default -> "Type inconnu";
};

Cas d'usage réel : Parser des réponses API

Avant (Java 8) : Code verbeux

public String handleApiResponse(ApiResponse response) {
    if (response instanceof SuccessResponse) {
        SuccessResponse success = (SuccessResponse) response;
        return "Success: " + success.getData();
    } else if (response instanceof ErrorResponse) {
        ErrorResponse error = (ErrorResponse) response;
        return "Error: " + error.getMessage();
    } else if (response instanceof WarningResponse) {
        WarningResponse warning = (WarningResponse) response;
        return "Warning: " + warning.getWarning();
    } else {
        return "Unknown response";
    }
}

Après (Java 17+) : Code élégant

public String handleApiResponse(ApiResponse response) {
    return switch (response) {
        case SuccessResponse s -> "Success: " + s.data();
        case ErrorResponse e -> "Error: " + e.message();
        case WarningResponse w -> "Warning: " + w.warning();
        case null -> "No response";
        default -> "Unknown response";
    };
}

-60% de code, +100% de lisibilité.

Code

// ========== PATTERN MATCHING : AVANT / APRÈS ==========

// ========== JAVA 8 : Verbeux ========== public void processPayment(Payment payment) { if (payment instanceof CreditCardPayment) { CreditCardPayment cc = (CreditCardPayment) payment; System.out.println("Carte: " + cc.getCardNumber()); processCreditCard(cc); } else if (payment instanceof MobileMoneyPayment) { MobileMoneyPayment mm = (MobileMoneyPayment) payment; System.out.println("Mobile: " + mm.getPhoneNumber()); processMobileMoney(mm); } else if (payment instanceof BankTransferPayment) { BankTransferPayment bt = (BankTransferPayment) payment; System.out.println("Banque: " + bt.getIban()); processBankTransfer(bt); } else { throw new UnsupportedOperationException("Unknown payment type"); } }

// ========== JAVA 17+ : Élégant ========== public void processPayment(Payment payment) { switch (payment) { case CreditCardPayment cc -> { System.out.println("Carte: " + cc.cardNumber()); processCreditCard(cc); } case MobileMoneyPayment mm -> { System.out.println("Mobile: " + mm.phoneNumber()); processMobileMoney(mm); } case BankTransferPayment bt -> { System.out.println("Banque: " + bt.iban()); processBankTransfer(bt); } case null -> throw new IllegalArgumentException("Payment cannot be null"); default -> throw new UnsupportedOperationException("Unknown payment type"); } }

// ========== AVEC GUARDS (when) ========== public String calculateDiscount(Order order) { return switch (order) { case Order o when o.total() > 1000 -> "Réduction 20%"; case Order o when o.total() > 500 -> "Réduction 10%"; case Order o when o.total() > 100 -> "Réduction 5%"; default -> "Pas de réduction"; }; }

Java Pattern Matching - Code plus élégant

Java Pattern Matching - Code plus élégant

Migration Java 8 → 17/21 : Guide Pas à Pas

"J'ai un projet Java 8. Comment migrer sans tout casser ?"

C'est LA question que tout le monde pose. Voici le guide complet, étape par étape.

ÉTAPE 1 : Audit de ton projet

Avant de commencer, vérifie :

# Vérifier la version Java actuelle
java -version
javac -version

# Vérifier les dépendances obsolètes mvn versions:display-dependency-updates mvn versions:display-plugin-updates

# Lister les dépendances directes mvn dependency:tree

Note les dépendances obsolètes, tu devras les mettre à jour.

ÉTAPE 2 : Les dépendances problématiques

Certaines dépendances ont été retirées de Java 11+ :

Module retiréProblèmeSolution
JAXBAPI XML BindingAjouter jakarta.xml.bind-api
JAX-WSWeb Services SOAPAjouter jakarta.xml.ws-api
javax.annotation@PostConstruct, @PreDestroyAjouter jakarta.annotation-api
CORBARMI/IIOPMigrer vers REST/gRPC
JavaFXInterface graphiqueUtiliser JavaFX SDK séparé

ÉTAPE 3 : Mettre à jour pom.xml

<!-- AVANT (Java 8) -->
<properties>
    <java.version>1.8</java.version>
    <spring-boot.version>2.7.18</spring-boot.version>
</properties>

<!-- APRÈS (Java 17) --> <properties> <java.version>17</java.version> <maven.compiler.release>17</maven.compiler.release> <spring-boot.version>3.2.0</spring-boot.version> </properties>

ÉTAPE 4 : Migrer javax → jakarta

Spring Boot 3+ utilise Jakarta EE 9+, qui a renommé tous les packages javax.* en jakarta.*.

Rechercher/Remplacer dans tout le projet :

// Remplacer
import javax.persistence.*;
import javax.validation.*;
import javax.servlet.*;

// Par import jakarta.persistence.*; import jakarta.validation.*; import jakarta.servlet.*;

Dans IntelliJ IDEA / VS Code :

  • Ctrl+Shift+R (ou Cmd+Shift+R sur Mac)
  • Rechercher : javax.
  • Remplacer par : jakarta.

ÉTAPE 5 : Mettre à jour les dépendances

Certaines bibliothèques ont des versions incompatibles avec Java 17 :

BibliothèqueVersion Java 8Version Java 17+
Lombok< 1.18.20>= 1.18.30
Hibernate< 5.6>= 5.6 ou 6.x
Jackson< 2.13>= 2.15
MapStruct< 1.4>= 1.5

ÉTAPE 6 : Compiler et tester

# Compiler avec Java 17
mvn clean compile

# Si erreurs, lis les messages et corrige

# Lancer les tests mvn test

# Tests d'intégration mvn verify

Timeline recommandée

  • Petit projet (< 10k lignes) : 1-2 semaines
  • Projet moyen (10k-100k lignes) : 1-2 mois
  • Grand projet (> 100k lignes) : 3-6 mois

Fais-le progressivement, module par module, pas tout d'un coup !

Code

// ========== CHECKLIST MIGRATION JAVA 8 → 17 ==========

// 1. pom.xml : Mettre à jour la version Java 17 17

// 2. Spring Boot : Migrer vers 3.x org.springframework.boot spring-boot-starter-parent 3.2.0

// 3. Ajouter les dépendances retirées jakarta.xml.bind jakarta.xml.bind-api 4.0.1 jakarta.annotation jakarta.annotation-api 2.1.1

// 4. Migrer javax → jakarta // Rechercher/Remplacer dans tous les fichiers : import javax.persistence.*; → import jakarta.persistence.*; import javax.validation.*; → import jakarta.validation.*; import javax.servlet.*; → import jakarta.servlet.*;

// 5. Mettre à jour Lombok org.projectlombok lombok 1.18.30

// 6. Compiler mvn clean compile

// 7. Tester mvn test

// 8. (Optionnel) Activer Virtual Threads si Java 21 # application.yml spring: threads: virtual: enabled: true

Guide de migration Java 8 vers Java 21

Guide de migration Java 8 vers Java 21