✦ 112 patterns modernes · Java 8 → Java 25

Java a évolué.
Votre code peut aussi.

Une collection de snippets Java modernes. Chaque ancien pattern Java à côté de son remplacement moderne et propre — côte à côte.

✕ Ancien
public class Point {
    private final int x, y;
    public Point(int x, int y) { ... }
    public int getX() { return x; }
    public int getY() { return y; }
    // equals, hashCode, toString
}
✓ Moderne
public record Point(int x, int y) {}
Partager 𝕏 🦋 in
🤖
Modernisez votre code Java avec GitHub Copilot. Laissez Copilot vous aider à migrer les patterns legacy vers Java moderne — automatiquement.

Toutes les comparaisons

112 snippets
Afficher :
Language

Constructeur canonique compact

Ancien public record Person(String name, List<String> pets) { // Full canonical constructor public Person(String name, List<String> pets) { Objects.requireNonNull(name); this.name = name; this.pets = List.copyOf(pets); } }
Moderne public record Person(String name, List<String> pets) { // Compact constructor public Person { Objects.requireNonNull(name); pets = List.copyOf(pets); } }
survolez pour voir le moderne →
Language

Fichiers source compacts

Ancien public class HelloWorld { public static void main(String[] args) { System.out.println( "Hello, World!"); } }
Moderne void main() { IO.println("Hello, World!"); }
survolez pour voir le moderne →
Language

Méthodes default dans les interfaces

Ancien // Need abstract class to share behavior public abstract class AbstractLogger { public void log(String msg) { System.out.println( timestamp() + ": " + msg); } abstract String timestamp(); } // Single inheritance only public class FileLogger extends AbstractLogger { ... }
Moderne public interface Logger { default void log(String msg) { IO.println( timestamp() + ": " + msg); } String timestamp(); } // Multiple interfaces allowed public class FileLogger implements Logger, Closeable { ... }
survolez pour voir le moderne →
Language

Opérateur diamond avec les classes anonymes

Ancien Map<String, List<String>> map = new HashMap<String, List<String>>(); // anonymous class: no diamond Predicate<String> p = new Predicate<String>() { public boolean test(String s) {..} };
Moderne Map<String, List<String>> map = new HashMap<>(); // Java 9: diamond with anonymous classes Predicate<String> p = new Predicate<>() { public boolean test(String s) {..} };
survolez pour voir le moderne →
Language

switch exhaustif sans default

Ancien // Must add default even though // all cases are covered double area(Shape s) { if (s instanceof Circle c) return Math.PI * c.r() * c.r(); else if (s instanceof Rect r) return r.w() * r.h(); else throw new IAE(); }
Moderne // sealed Shape permits Circle, Rect double area(Shape s) { return switch (s) { case Circle c -> Math.PI * c.r() * c.r(); case Rect r -> r.w() * r.h(); }; // no default needed! }
survolez pour voir le moderne →
Language

Corps de constructeur flexibles

Ancien class Square extends Shape { Square(double side) { super(side, side); // can't validate BEFORE super! if (side <= 0) throw new IAE("bad"); } }
Moderne class Square extends Shape { Square(double side) { if (side <= 0) throw new IAE("bad"); super(side, side); } }
survolez pour voir le moderne →
Language

Patterns avec gardes when

Ancien if (shape instanceof Circle c) { if (c.radius() > 10) { return "large circle"; } else { return "small circle"; } } else { return "not a circle"; }
Moderne return switch (shape) { case Circle c when c.radius() > 10 -> "large circle"; case Circle c -> "small circle"; default -> "not a circle"; };
survolez pour voir le moderne →
Language

Markdown dans les commentaires Javadoc

Ancien /** * Returns the {@code User} with * the given ID. * * <p>Example: * <pre>{@code * var user = findUser(123); * }</pre> * * @param id the user ID * @return the user */ public User findUser(int id) { ... }
Moderne /// Returns the `User` with /// the given ID. /// /// Example: /// ```java /// var user = findUser(123); /// ``` /// /// @param id the user ID /// @return the user public User findUser(int id) { ... }
survolez pour voir le moderne →
Language

Déclarations d'import de modules

Ancien import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path;
Moderne import module java.base; // All of java.util, java.io, java.nio // etc. available in one line
survolez pour voir le moderne →
Language

Pattern matching pour instanceof

Ancien if (obj instanceof String) { String s = (String) obj; System.out.println(s.length()); }
Moderne if (obj instanceof String s) { IO.println(s.length()); }
survolez pour voir le moderne →
Language

Pattern matching dans switch

Ancien String format(Object obj) { if (obj instanceof Integer i) return "int: " + i; else if (obj instanceof Double d) return "double: " + d; else if (obj instanceof String s) return "str: " + s; return "unknown"; }
Moderne String format(Object obj) { return switch (obj) { case Integer i -> "int: " + i; case Double d -> "double: " + d; case String s -> "str: " + s; default -> "unknown"; }; }
survolez pour voir le moderne →
Language

Types primitifs dans les patterns

Ancien String classify(int code) { if (code >= 200 && code < 300) return "success"; else if (code >= 400 && code < 500) return "client error"; else return "other"; }
Moderne String classify(int code) { return switch (code) { case int c when c >= 200 && c < 300 -> "success"; case int c when c >= 400 && c < 500 -> "client error"; default -> "other"; }; }
survolez pour voir le moderne →
Language

Méthodes privées dans les interfaces

Ancien interface Logger { default void logInfo(String msg) { System.out.println( "[INFO] " + timestamp() + msg); } default void logWarn(String msg) { System.out.println( "[WARN] " + timestamp() + msg); } }
Moderne interface Logger { private String format(String lvl, String msg) { return "[" + lvl + "] " + timestamp() + msg; } default void logInfo(String msg) { IO.println(format("INFO", msg)); } default void logWarn(String msg) { IO.println(format("WARN", msg)); } }
survolez pour voir le moderne →
Language

Patterns de record (déstructuration)

Ancien if (obj instanceof Point) { Point p = (Point) obj; int x = p.getX(); int y = p.getY(); System.out.println(x + y); }
Moderne if (obj instanceof Point(int x, int y)) { IO.println(x + y); }
survolez pour voir le moderne →
Language

Records pour les classes de données

Ancien public class Point { private final int x, y; public Point(int x, int y) { ... } public int getX() { return x; } public int getY() { return y; } // equals, hashCode, toString }
Moderne public record Point(int x, int y) {}
survolez pour voir le moderne →
Language

Classes sealed pour les hiérarchies de types

Ancien // Anyone can extend Shape public abstract class Shape { } public class Circle extends Shape { } public class Rect extends Shape { } // unknown subclasses possible
Moderne public sealed interface Shape permits Circle, Rect {} public record Circle(double r) implements Shape {} public record Rect(double w, double h) implements Shape {}
survolez pour voir le moderne →
Language

Membres statiques dans les classes internes

Ancien class Library { // Must be static nested class static class Book { static int globalBookCount; Book() { globalBookCount++; } } } // Usage var book = new Library.Book();
Moderne class Library { // Can be inner class with statics class Book { static int globalBookCount; Book() { Book.globalBookCount++; } } } // Usage var lib = new Library(); var book = lib.new Book();
survolez pour voir le moderne →
Language

Méthodes statiques dans les interfaces

Ancien // Separate utility class needed public class ValidatorUtils { public static boolean isBlank( String s) { return s == null || s.trim().isEmpty(); } } // Usage if (ValidatorUtils.isBlank(input)) { ... }
Moderne public interface Validator { boolean validate(String s); static boolean isBlank(String s) { return s == null || s.trim().isEmpty(); } } // Usage if (Validator.isBlank(input)) { ... }
survolez pour voir le moderne →
Language

Expressions switch

Ancien String msg; switch (day) { case MONDAY: msg = "Start"; break; case FRIDAY: msg = "End"; break; default: msg = "Mid"; }
Moderne String msg = switch (day) { case MONDAY -> "Start"; case FRIDAY -> "End"; default -> "Mid"; };
survolez pour voir le moderne →
Language

Blocs de texte pour les chaînes multiligne

Ancien String json = "{\n" + " \"name\": \"Duke\",\n" + " \"age\": 30\n" + "}";
Moderne String json = """ { "name": "Duke", "age": 30 }""";
survolez pour voir le moderne →
Language

Inférence de type avec var

Ancien Map<String, List<Integer>> map = new HashMap<String, List<Integer>>(); for (Map.Entry<String, List<Integer>> e : map.entrySet()) { // verbose type noise }
Moderne var map = new HashMap<String, List<Integer>>(); for (var entry : map.entrySet()) { // clean and readable }
survolez pour voir le moderne →
Language

Variables sans nom avec _

Ancien try { parse(input); } catch (Exception ignored) { log("parse failed"); } map.forEach((key, value) -> { process(value); // key unused });
Moderne try { parse(input); } catch (Exception _) { log("parse failed"); } map.forEach((_, value) -> { process(value); });
survolez pour voir le moderne →
Collections

Collectors.teeing()

Ancien long count = items.stream().count(); double sum = items.stream() .mapToDouble(Item::price) .sum(); var result = new Stats(count, sum);
Moderne var result = items.stream().collect( Collectors.teeing( Collectors.counting(), Collectors.summingDouble(Item::price), Stats::new ) );
survolez pour voir le moderne →
Collections

Copie immutable de collections

Ancien List<String> copy = Collections.unmodifiableList( new ArrayList<>(original) );
Moderne List<String> copy = List.copyOf(original);
survolez pour voir le moderne →
Collections

Création de listes immutables

Ancien List<String> list = Collections.unmodifiableList( new ArrayList<>( Arrays.asList("a", "b", "c") ) );
Moderne List<String> list = List.of("a", "b", "c");
survolez pour voir le moderne →
Collections

Création de maps immutables

Ancien Map<String, Integer> map = new HashMap<>(); map.put("a", 1); map.put("b", 2); map.put("c", 3); map = Collections.unmodifiableMap(map);
Moderne Map<String, Integer> map = Map.of("a", 1, "b", 2, "c", 3);
survolez pour voir le moderne →
Collections

Création de sets immutables

Ancien Set<String> set = Collections.unmodifiableSet( new HashSet<>( Arrays.asList("a", "b", "c") ) );
Moderne Set<String> set = Set.of("a", "b", "c");
survolez pour voir le moderne →
Collections

Factory Map.entry()

Ancien Map.Entry<String, Integer> e = new AbstractMap.SimpleEntry<>( "key", 42 );
Moderne var e = Map.entry("key", 42);
survolez pour voir le moderne →
Collections

Itération inverse de listes

Ancien for (ListIterator<String> it = list.listIterator(list.size()); it.hasPrevious(); ) { String element = it.previous(); System.out.println(element); }
Moderne for (String element : list.reversed()) { IO.println(element); }
survolez pour voir le moderne →
Collections

Collections séquencées

Ancien // Get last element var last = list.get(list.size() - 1); // Get first var first = list.get(0); // Reverse iteration: manual
Moderne var last = list.getLast(); var first = list.getFirst(); var reversed = list.reversed();
survolez pour voir le moderne →
Collections

toArray typé dans les streams

Ancien List<String> list = getNames(); String[] arr = new String[list.size()]; for (int i = 0; i < list.size(); i++) { arr[i] = list.get(i); }
Moderne String[] arr = getNames().stream() .filter(n -> n.length() > 3) .toArray(String[]::new);
survolez pour voir le moderne →
Collections

Collectors non modifiables

Ancien List<String> list = stream.collect( Collectors.collectingAndThen( Collectors.toList(), Collections::unmodifiableList ) );
Moderne List<String> list = stream.toList();
survolez pour voir le moderne →
Strings

Caractères de String comme stream

Ancien for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (Character.isDigit(c)) { process(c); } }
Moderne str.chars() .filter(Character::isDigit) .forEach(c -> process((char) c));
survolez pour voir le moderne →
Strings

String.formatted()

Ancien String msg = String.format( "Hello %s, you are %d", name, age );
Moderne String msg = "Hello %s, you are %d" .formatted(name, age);
survolez pour voir le moderne →
Strings

String.indent() et transform()

Ancien String[] lines = text.split("\n"); StringBuilder sb = new StringBuilder(); for (String line : lines) { sb.append(" ").append(line) .append("\n"); } String indented = sb.toString();
Moderne String indented = text.indent(4); String result = text .transform(String::strip) .transform(s -> s.replace(" ", "-"));
survolez pour voir le moderne →
Strings

String.isBlank()

Ancien boolean blank = str.trim().isEmpty(); // or: str.trim().length() == 0
Moderne boolean blank = str.isBlank(); // handles Unicode whitespace too
survolez pour voir le moderne →
Strings

String.lines() pour diviser les lignes

Ancien String text = "one\ntwo\nthree"; String[] lines = text.split("\n"); for (String line : lines) { System.out.println(line); }
Moderne String text = "one\ntwo\nthree"; text.lines().forEach(IO::println);
survolez pour voir le moderne →
Strings

String.repeat()

Ancien StringBuilder sb = new StringBuilder(); for (int i = 0; i < 3; i++) { sb.append("abc"); } String result = sb.toString();
Moderne String result = "abc".repeat(3); // "abcabcabc"
survolez pour voir le moderne →
Strings

String.strip() vs trim()

Ancien // trim() only removes ASCII whitespace // (chars <= U+0020) String clean = str.trim();
Moderne // strip() removes all Unicode whitespace String clean = str.strip(); String left = str.stripLeading(); String right = str.stripTrailing();
survolez pour voir le moderne →
Streams

Collectors.flatMapping()

Ancien // Flatten within a grouping collector // Required complex custom collector Map<String, Set<String>> tagsByDept = // no clean way in Java 8
Moderne var tagsByDept = employees.stream() .collect(groupingBy( Emp::dept, flatMapping( e -> e.tags().stream(), toSet() ) ));
survolez pour voir le moderne →
Streams

Optional.ifPresentOrElse()

Ancien Optional<User> user = findUser(id); if (user.isPresent()) { greet(user.get()); } else { handleMissing(); }
Moderne findUser(id).ifPresentOrElse( this::greet, this::handleMissing );
survolez pour voir le moderne →
Streams

Optional.or() comme alternative

Ancien Optional<Config> cfg = primary(); if (!cfg.isPresent()) { cfg = secondary(); } if (!cfg.isPresent()) { cfg = defaults(); }
Moderne Optional<Config> cfg = primary() .or(this::secondary) .or(this::defaults);
survolez pour voir le moderne →
Streams

Predicate.not() pour la négation

Ancien List<String> nonEmpty = list.stream() .filter(s -> !s.isBlank()) .collect(Collectors.toList());
Moderne List<String> nonEmpty = list.stream() .filter(Predicate.not(String::isBlank)) .toList();
survolez pour voir le moderne →
Streams

Stream gatherers

Ancien // Sliding window: manual implementation List<List<T>> windows = new ArrayList<>(); for (int i = 0; i <= list.size()-3; i++) { windows.add( list.subList(i, i + 3)); }
Moderne var windows = stream .gather( Gatherers.windowSliding(3) ) .toList();
survolez pour voir le moderne →
Streams

Stream.iterate() avec prédicat

Ancien Stream.iterate(1, n -> n * 2) .limit(10) .forEach(System.out::println); // can't stop at a condition
Moderne Stream.iterate( 1, n -> n < 1000, n -> n * 2 ).forEach(IO::println); // stops when n >= 1000
survolez pour voir le moderne →
Streams

Stream.mapMulti()

Ancien stream.flatMap(order -> order.items().stream() .map(item -> new OrderItem( order.id(), item) ) );
Moderne stream.<OrderItem>mapMulti( (order, downstream) -> { for (var item : order.items()) downstream.accept( new OrderItem(order.id(), item)); } );
survolez pour voir le moderne →
Streams

Stream.ofNullable()

Ancien Stream<String> s = val != null ? Stream.of(val) : Stream.empty();
Moderne Stream<String> s = Stream.ofNullable(val);
survolez pour voir le moderne →
Streams

Stream takeWhile / dropWhile

Ancien List<Integer> result = new ArrayList<>(); for (int n : sorted) { if (n >= 100) break; result.add(n); } // no stream equivalent in Java 8
Moderne var result = sorted.stream() .takeWhile(n -> n < 100) .toList(); // or: .dropWhile(n -> n < 10)
survolez pour voir le moderne →
Streams

Stream.toList()

Ancien List<String> result = stream .filter(s -> s.length() > 3) .collect(Collectors.toList());
Moderne List<String> result = stream .filter(s -> s.length() > 3) .toList();
survolez pour voir le moderne →
Streams

Executor avec threads virtuels

Ancien ExecutorService exec = Executors.newFixedThreadPool(10); try { futures = tasks.stream() .map(t -> exec.submit(t)) .toList(); } finally { exec.shutdown(); }
Moderne try (var exec = Executors .newVirtualThreadPerTaskExecutor()) { var futures = tasks.stream() .map(exec::submit) .toList(); }
survolez pour voir le moderne →
Concurrency

Chaînage avec CompletableFuture

Ancien Future<String> future = executor.submit(this::fetchData); String data = future.get(); // blocks String result = transform(data);
Moderne CompletableFuture.supplyAsync( this::fetchData ) .thenApply(this::transform) .thenAccept(IO::println);
survolez pour voir le moderne →
Concurrency

HTTP concurrent avec les threads virtuels

Ancien ExecutorService pool = Executors.newFixedThreadPool(10); List<Future<String>> futures = urls.stream() .map(u -> pool.submit( () -> fetchUrl(u))) .toList(); // manual shutdown, blocking get()
Moderne try (var exec = Executors .newVirtualThreadPerTaskExecutor()) { var results = urls.stream() .map(u -> exec.submit( () -> client.send(req(u), ofString()).body())) .toList().stream() .map(Future::join).toList(); }
survolez pour voir le moderne →
Concurrency

Fermeture automatique d'ExecutorService

Ancien ExecutorService exec = Executors.newCachedThreadPool(); try { exec.submit(task); } finally { exec.shutdown(); exec.awaitTermination( 1, TimeUnit.MINUTES); }
Moderne try (var exec = Executors.newCachedThreadPool()) { exec.submit(task); } // auto shutdown + await on close
survolez pour voir le moderne →
Concurrency

Initialisation paresseuse sans verrous

Ancien class Config { private static volatile Config inst; static Config get() { if (inst == null) { synchronized (Config.class) { if (inst == null) inst = load(); } } return inst; } }
Moderne class Config { private static final StableValue<Config> INST = StableValue.of(Config::load); static Config get() { return INST.get(); } }
survolez pour voir le moderne →
Concurrency

API moderne des processus

Ancien Process p = Runtime.getRuntime() .exec("ls -la"); int code = p.waitFor(); // no way to get PID // no easy process info
Moderne ProcessHandle ph = ProcessHandle.current(); long pid = ph.pid(); ph.info().command() .ifPresent(IO::println); ph.children().forEach( c -> IO.println(c.pid()));
survolez pour voir le moderne →
Concurrency

Valeurs à portée

Ancien static final ThreadLocal<User> CURRENT = new ThreadLocal<>(); void handle(Request req) { CURRENT.set(authenticate(req)); try { process(); } finally { CURRENT.remove(); } }
Moderne static final ScopedValue<User> CURRENT = ScopedValue.newInstance(); void handle(Request req) { ScopedValue.where(CURRENT, authenticate(req) ).run(this::process); }
survolez pour voir le moderne →
Concurrency

Valeurs stables

Ancien private volatile Logger logger; Logger getLogger() { if (logger == null) { synchronized (this) { if (logger == null) logger = createLogger(); } } return logger; }
Moderne private final StableValue<Logger> logger = StableValue.of(this::createLogger); Logger getLogger() { return logger.get(); }
survolez pour voir le moderne →
Concurrency

Concurrence structurée

Ancien ExecutorService exec = Executors.newFixedThreadPool(2); Future<User> u = exec.submit(this::fetchUser); Future<Order> o = exec.submit(this::fetchOrder); try { return combine(u.get(), o.get()); } finally { exec.shutdown(); }
Moderne try (var scope = new StructuredTaskScope .ShutdownOnFailure()) { var u = scope.fork(this::fetchUser); var o = scope.fork(this::fetchOrder); scope.join().throwIfFailed(); return combine(u.get(), o.get()); }
survolez pour voir le moderne →
Concurrency

Thread.sleep avec Duration

Ancien // What unit is 5000? ms? us? Thread.sleep(5000); // 2.5 seconds: math required Thread.sleep(2500);
Moderne Thread.sleep( Duration.ofSeconds(5) ); Thread.sleep( Duration.ofMillis(2500) );
survolez pour voir le moderne →
Concurrency

Threads virtuels

Ancien Thread thread = new Thread(() -> { System.out.println("hello"); }); thread.start(); thread.join();
Moderne Thread.startVirtualThread(() -> { IO.println("hello"); }).join();
survolez pour voir le moderne →
I/O

Filtres de désérialisation

Ancien // Dangerous: accepts any class ObjectInputStream ois = new ObjectInputStream(input); Object obj = ois.readObject(); // deserialization attacks possible!
Moderne ObjectInputFilter filter = ObjectInputFilter.Config .createFilter( "com.myapp.*;!*" ); ois.setObjectInputFilter(filter); Object obj = ois.readObject();
survolez pour voir le moderne →
I/O

Mapping de fichiers en mémoire

Ancien try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE)) { MappedByteBuffer buffer = channel.map( FileChannel.MapMode.READ_WRITE, 0, (int) channel.size()); // Limited to 2GB // Freed by GC, no control }
Moderne FileChannel channel = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE); try (Arena arena = Arena.ofShared()) { MemorySegment segment = channel.map( FileChannel.MapMode.READ_WRITE, 0, channel.size(), arena); // No size limit // ... } // Deterministic cleanup
survolez pour voir le moderne →
I/O

Files.mismatch()

Ancien // Compare two files byte by byte byte[] f1 = Files.readAllBytes(path1); byte[] f2 = Files.readAllBytes(path2); boolean equal = Arrays.equals(f1, f2); // loads both files entirely into memory
Moderne long pos = Files.mismatch(path1, path2); // -1 if identical // otherwise: position of first difference
survolez pour voir le moderne →
I/O

Client HTTP moderne

Ancien URL url = new URL("https://api.com/data"); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); BufferedReader in = new BufferedReader( new InputStreamReader(con.getInputStream())); // read lines, close streams...
Moderne var client = HttpClient.newHttpClient(); var request = HttpRequest.newBuilder() .uri(URI.create("https://api.com/data")) .build(); var response = client.send( request, BodyHandlers.ofString()); String body = response.body();
survolez pour voir le moderne →
I/O

InputStream.transferTo()

Ancien byte[] buf = new byte[8192]; int n; while ((n = input.read(buf)) != -1) { output.write(buf, 0, n); }
Moderne input.transferTo(output);
survolez pour voir le moderne →
I/O

Classe IO pour les entrées/sorties console

Ancien import java.util.Scanner; Scanner sc = new Scanner(System.in); System.out.print("Name: "); String name = sc.nextLine(); System.out.println("Hello, " + name); sc.close();
Moderne String name = IO.readln("Name: "); IO.println("Hello, " + name);
survolez pour voir le moderne →
I/O

Méthode factory Path.of()

Ancien Path path = Paths.get("src", "main", "java", "App.java");
Moderne var path = Path.of("src", "main", "java", "App.java");
survolez pour voir le moderne →
I/O

Lecture de fichiers

Ancien StringBuilder sb = new StringBuilder(); try (BufferedReader br = new BufferedReader( new FileReader("data.txt"))) { String line; while ((line = br.readLine()) != null) sb.append(line).append("\n"); } String content = sb.toString();
Moderne String content = Files.readString(Path.of("data.txt"));
survolez pour voir le moderne →
I/O

Amélioration de try-with-resources

Ancien Connection conn = getConnection(); // Must re-declare in try try (Connection c = conn) { use(c); }
Moderne Connection conn = getConnection(); // Use existing variable directly try (conn) { use(conn); }
survolez pour voir le moderne →
I/O

Écriture de fichiers

Ancien try (FileWriter fw = new FileWriter("out.txt"); BufferedWriter bw = new BufferedWriter(fw)) { bw.write(content); }
Moderne Files.writeString( Path.of("out.txt"), content );
survolez pour voir le moderne →
Errors

NullPointerExceptions descriptives

Ancien // Old NPE message: // "NullPointerException" // at MyApp.main(MyApp.java:42) // Which variable was null?!
Moderne // Modern NPE message: // Cannot invoke "String.length()" // because "user.address().city()" // is null // Exact variable identified!
survolez pour voir le moderne →
Errors

Gestion d'exceptions avec multi-catch

Ancien try { process(); } catch (IOException e) { log(e); } catch (SQLException e) { log(e); } catch (ParseException e) { log(e); }
Moderne try { process(); } catch (IOException | SQLException | ParseException e) { log(e); }
survolez pour voir le moderne →
Errors

Cas null dans switch

Ancien // Must check before switch if (status == null) { return "unknown"; } return switch (status) { case ACTIVE -> "active"; case PAUSED -> "paused"; default -> "other"; };
Moderne return switch (status) { case null -> "unknown"; case ACTIVE -> "active"; case PAUSED -> "paused"; default -> "other"; };
survolez pour voir le moderne →
Errors

Chaînage avec Optional

Ancien String city = null; if (user != null) { Address addr = user.getAddress(); if (addr != null) { city = addr.getCity(); } } if (city == null) city = "Unknown";
Moderne String city = Optional.ofNullable(user) .map(User::address) .map(Address::city) .orElse("Unknown");
survolez pour voir le moderne →
Errors

Optional.orElseThrow() sans supplier

Ancien // Risky: get() throws if empty, no clear intent String value = optional.get(); // Verbose: supplier just for NoSuchElementException String value = optional .orElseThrow(NoSuchElementException::new);
Moderne // Clear intent: throws NoSuchElementException if empty String value = optional.orElseThrow();
survolez pour voir le moderne →
Errors

Réponses d'erreur basées sur des records

Ancien // Verbose error class public class ErrorResponse { private final int code; private final String message; // constructor, getters, equals, // hashCode, toString... }
Moderne public record ApiError( int code, String message, Instant timestamp ) { public ApiError(int code, String msg) { this(code, msg, Instant.now()); } }
survolez pour voir le moderne →
Errors

Objects.requireNonNullElse()

Ancien String name = input != null ? input : "default"; // easy to get the order wrong
Moderne String name = Objects .requireNonNullElse( input, "default" );
survolez pour voir le moderne →
Date/Time

Formatage de dates

Ancien // Not thread-safe! SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String formatted = sdf.format(date); // Must synchronize for concurrent use
Moderne DateTimeFormatter fmt = DateTimeFormatter.ofPattern( "uuuu-MM-dd"); String formatted = LocalDate.now().format(fmt); // Thread-safe, immutable
survolez pour voir le moderne →
Date/Time

Duration et Period

Ancien // How many days between two dates? long diff = date2.getTime() - date1.getTime(); long days = diff / (1000 * 60 * 60 * 24); // ignores DST, leap seconds
Moderne long days = ChronoUnit.DAYS .between(date1, date2); Period period = Period.between( date1, date2); Duration elapsed = Duration.between( time1, time2);
survolez pour voir le moderne →
Date/Time

HexFormat

Ancien // Pad to 2 digits, uppercase String hex = String.format( "%02X", byteValue); // Parse hex string int val = Integer.parseInt( "FF", 16);
Moderne var hex = HexFormat.of() .withUpperCase(); String s = hex.toHexDigits( byteValue); byte[] bytes = hex.parseHex("48656C6C6F");
survolez pour voir le moderne →
Date/Time

Instant avec précision en nanosecondes

Ancien // Millisecond precision only long millis = System.currentTimeMillis(); // 1708012345678
Moderne // Microsecond/nanosecond precision Instant now = Instant.now(); // 2025-02-15T20:12:25.678901234Z long nanos = now.getNano();
survolez pour voir le moderne →
Date/Time

Fondamentaux de l'API java.time

Ancien // Mutable, confusing, zero-indexed months Calendar cal = Calendar.getInstance(); cal.set(2025, 0, 15); // January = 0! Date date = cal.getTime(); // not thread-safe
Moderne LocalDate date = LocalDate.of( 2025, Month.JANUARY, 15); LocalTime time = LocalTime.of(14, 30); Instant now = Instant.now(); // immutable, thread-safe
survolez pour voir le moderne →
Date/Time

Math.clamp()

Ancien // Clamp value between min and max int clamped = Math.min(Math.max(value, 0), 100); // or: min and max order confusion
Moderne int clamped = Math.clamp(value, 0, 100); // value constrained to [0, 100]
survolez pour voir le moderne →
Security

Fonctions de dérivation de clés

Ancien SecretKeyFactory factory = SecretKeyFactory.getInstance( "PBKDF2WithHmacSHA256"); KeySpec spec = new PBEKeySpec( password, salt, 10000, 256); SecretKey key = factory.generateSecret(spec);
Moderne var kdf = KDF.getInstance("HKDF-SHA256"); SecretKey key = kdf.deriveKey( "AES", KDF.HKDFParameterSpec .ofExtract() .addIKM(inputKey) .addSalt(salt) .thenExpand(info, 32) .build() );
survolez pour voir le moderne →
Security

Encodage/décodage PEM

Ancien String pem = "-----BEGIN CERTIFICATE-----\n" + Base64.getMimeEncoder() .encodeToString( cert.getEncoded()) + "\n-----END CERTIFICATE-----";
Moderne // Encode to PEM String pem = PEMEncoder.of() .encodeToString(cert); // Decode from PEM var cert = PEMDecoder.of() .decode(pemString);
survolez pour voir le moderne →
Security

Interface RandomGenerator

Ancien // Hard-coded to one algorithm Random rng = new Random(); int value = rng.nextInt(100); // Or thread-local, but still locked in int value = ThreadLocalRandom.current() .nextInt(100);
Moderne // Algorithm-agnostic via factory var rng = RandomGenerator.of("L64X128MixRandom"); int value = rng.nextInt(100); // Or get a splittable generator var rng = RandomGeneratorFactory .of("L64X128MixRandom").create();
survolez pour voir le moderne →
Security

Génération de nombres aléatoires forts

Ancien // Default algorithm — may not be // the strongest available SecureRandom random = new SecureRandom(); byte[] bytes = new byte[32]; random.nextBytes(bytes);
Moderne // Platform's strongest algorithm SecureRandom random = SecureRandom.getInstanceStrong(); byte[] bytes = new byte[32]; random.nextBytes(bytes);
survolez pour voir le moderne →
Security

TLS 1.3 par défaut

Ancien SSLContext ctx = SSLContext.getInstance("TLSv1.2"); ctx.init(null, trustManagers, null); SSLSocketFactory factory = ctx.getSocketFactory(); // Must specify protocol version
Moderne // TLS 1.3 is the default! var client = HttpClient.newBuilder() .sslContext(SSLContext.getDefault()) .build(); // Already using TLS 1.3
survolez pour voir le moderne →
Tooling

Préchargement de classes AOT

Ancien // Every startup: // - Load 10,000+ classes // - Verify bytecode // - JIT compile hot paths // Startup: 2-5 seconds
Moderne // Training run: $ java -XX:AOTCacheOutput=app.aot \ -cp app.jar com.App // Production: $ java -XX:AOTCache=app.aot \ -cp app.jar com.App
survolez pour voir le moderne →
Tooling

Serveur HTTP intégré

Ancien // Install and configure a web server // (Apache, Nginx, or embedded Jetty) // Or write boilerplate with com.sun.net.httpserver HttpServer server = HttpServer.create( new InetSocketAddress(8080), 0); server.createContext("/", exchange -> { ... }); server.start();
Moderne // Terminal: serve current directory $ jwebserver // Or use the API (JDK 18+) var server = SimpleFileServer.createFileServer( new InetSocketAddress(8080), Path.of("."), OutputLevel.VERBOSE); server.start();
survolez pour voir le moderne →
Tooling

En-têtes d'objets compacts

Ancien // Default: 128-bit object header // = 16 bytes overhead per object // A boolean field object = 32 bytes! // Mark word (64) + Klass pointer (64)
Moderne // -XX:+UseCompactObjectHeaders // 64-bit object header // = 8 bytes overhead per object // 50% less header memory // More objects fit in cache
survolez pour voir le moderne →
Tooling

JFR pour le profilage

Ancien // Install VisualVM / YourKit / JProfiler // Attach to running process // Configure sampling // Export and analyze // External tool required
Moderne // Start with profiling enabled $ java -XX:StartFlightRecording= filename=rec.jfr MyApp // Or attach to running app: $ jcmd <pid> JFR.start
survolez pour voir le moderne →
Tooling

JShell pour le prototypage

Ancien // 1. Create Test.java // 2. javac Test.java // 3. java Test // Just to test one expression!
Moderne $ jshell jshell> "hello".chars().count() $1 ==> 5 jshell> List.of(1,2,3).reversed() $2 ==> [3, 2, 1]
survolez pour voir le moderne →
Tooling

JUnit 6 avec sécurité de nuls JSpecify

Ancien import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class UserServiceTest { // JUnit 5: no null contracts on the API // Can assertEquals() accept null? Check source... // Does fail(String) allow null message? Unknown. @Test void findUser_found() { // Is result nullable? API doesn't say User result = service.findById("u1"); assertNotNull(result); assertEquals("Alice", result.name()); } @Test void findUser_notFound() { // Hope this returns null, not throws... assertNull(service.findById("missing")); } }
Moderne import org.junit.jupiter.api.Test; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import static org.junit.jupiter.api.Assertions.*; @NullMarked // all refs non-null unless @Nullable class UserServiceTest { // JUnit 6 API is @NullMarked: // assertNull(@Nullable Object actual) // assertEquals(@Nullable Object, @Nullable Object) // fail(@Nullable String message) @Test void findUser_found() { // IDE warns: findById returns @Nullable User @Nullable User result = service.findById("u1"); assertNotNull(result); // narrows type to non-null assertEquals("Alice", result.name()); // safe } @Test void findUser_notFound() { @Nullable User result = service.findById("missing"); assertNull(result); // IDE confirms null expectation } }
survolez pour voir le moderne →
Tooling

Lanceur de code source multi-fichiers

Ancien $ javac *.java $ java Main // Must compile all files first // Need a build tool for dependencies
Moderne $ java Main.java // Automatically finds and compiles // other source files referenced // by Main.java
survolez pour voir le moderne →
Tooling

Exécution d'un fichier unique

Ancien $ javac HelloWorld.java $ java HelloWorld // Two steps every time
Moderne $ java HelloWorld.java // Compiles and runs in one step // Also works with shebangs: #!/usr/bin/java --source 25
survolez pour voir le moderne →
Enterprise

EJB Timer vs Jakarta Scheduler

Ancien @Stateless public class ReportGenerator { @Resource TimerService timerService; @PostConstruct public void init() { timerService.createCalendarTimer( new ScheduleExpression() .hour("2").minute("0")); } @Timeout public void generateReport(Timer timer) { // runs every day at 02:00 buildDailyReport(); } }
Moderne @ApplicationScoped public class ReportGenerator { @Resource ManagedScheduledExecutorService scheduler; @PostConstruct public void init() { scheduler.scheduleAtFixedRate( this::generateReport, 0, 24, TimeUnit.HOURS); } public void generateReport() { buildDailyReport(); } }
survolez pour voir le moderne →
Enterprise

EJB versus CDI

Ancien @Stateless public class OrderEJB { @EJB private InventoryEJB inventory; public void placeOrder(Order order) { // container-managed transaction inventory.reserve(order.getItem()); } }
Moderne @ApplicationScoped public class OrderService { @Inject private InventoryService inventory; @Transactional public void placeOrder(Order order) { inventory.reserve(order.getItem()); } }
survolez pour voir le moderne →
Enterprise

Mapping de JDBC ResultSet vs API Criteria de JPA

Ancien String sql = "SELECT * FROM users" + " WHERE status = ? AND age > ?"; try (Connection con = ds.getConnection(); PreparedStatement ps = con.prepareStatement(sql)) { ps.setString(1, status); ps.setInt(2, minAge); ResultSet rs = ps.executeQuery(); List<User> users = new ArrayList<>(); while (rs.next()) { User u = new User(); u.setId(rs.getLong("id")); u.setName(rs.getString("name")); u.setAge(rs.getInt("age")); users.add(u); } }
Moderne @PersistenceContext EntityManager em; public List<User> findActiveAboveAge( String status, int minAge) { var cb = em.getCriteriaBuilder(); var cq = cb.createQuery(User.class); var root = cq.from(User.class); cq.select(root).where( cb.equal(root.get("status"), status), cb.greaterThan(root.get("age"), minAge)); return em.createQuery(cq).getResultList(); }
survolez pour voir le moderne →
Enterprise

JDBC versus jOOQ

Ancien String sql = "SELECT id, name, email FROM users " + "WHERE department = ? AND salary > ?"; try (Connection con = ds.getConnection(); PreparedStatement ps = con.prepareStatement(sql)) { ps.setString(1, department); ps.setBigDecimal(2, minSalary); ResultSet rs = ps.executeQuery(); List<User> result = new ArrayList<>(); while (rs.next()) { result.add(new User( rs.getLong("id"), rs.getString("name"), rs.getString("email"))); } return result; }
Moderne DSLContext dsl = DSL.using(ds, SQLDialect.POSTGRES); return dsl .select(USERS.ID, USERS.NAME, USERS.EMAIL) .from(USERS) .where(USERS.DEPARTMENT.eq(department) .and(USERS.SALARY.gt(minSalary))) .fetchInto(User.class);
survolez pour voir le moderne →
Enterprise

JDBC versus JPA

Ancien String sql = "SELECT * FROM users WHERE id = ?"; try (Connection con = dataSource.getConnection(); PreparedStatement ps = con.prepareStatement(sql)) { ps.setLong(1, id); ResultSet rs = ps.executeQuery(); if (rs.next()) { User u = new User(); u.setId(rs.getLong("id")); u.setName(rs.getString("name")); } }
Moderne @PersistenceContext EntityManager em; public User findUser(Long id) { return em.find(User.class, id); } public List<User> findByName(String name) { return em.createQuery( "SELECT u FROM User u WHERE u.name = :name", User.class) .setParameter("name", name) .getResultList(); }
survolez pour voir le moderne →
Enterprise

Recherche JNDI vs Injection CDI

Ancien public class OrderService { private DataSource ds; public void init() throws NamingException { InitialContext ctx = new InitialContext(); ds = (DataSource) ctx.lookup( "java:comp/env/jdbc/OrderDB"); } public List<Order> findAll() throws SQLException { try (Connection con = ds.getConnection()) { // query orders } } }
Moderne @ApplicationScoped public class OrderService { @Inject @Resource(name = "jdbc/OrderDB") DataSource ds; public List<Order> findAll() throws SQLException { try (Connection con = ds.getConnection()) { // query orders } } }
survolez pour voir le moderne →
Enterprise

JPA versus Jakarta Data

Ancien @PersistenceContext EntityManager em; public User findById(Long id) { return em.find(User.class, id); } public List<User> findByName(String name) { return em.createQuery( "SELECT u FROM User u WHERE u.name = :name", User.class) .setParameter("name", name) .getResultList(); } public void save(User user) { em.persist(user); }
Moderne @Repository public interface Users extends CrudRepository<User, Long> { List<User> findByName(String name); }
survolez pour voir le moderne →
Enterprise

JSF Managed Bean vs CDI Named Bean

Ancien @ManagedBean @SessionScoped public class UserBean implements Serializable { @ManagedProperty("#{userService}") private UserService userService; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void setUserService(UserService svc) { this.userService = svc; } }
Moderne @Named @SessionScoped public class UserBean implements Serializable { @Inject private UserService userService; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
survolez pour voir le moderne →
Enterprise

Transaction JPA manuelle vs @Transactional déclaratif

Ancien @PersistenceContext EntityManager em; public void transferFunds(Long from, Long to, BigDecimal amount) { EntityTransaction tx = em.getTransaction(); tx.begin(); try { Account src = em.find(Account.class, from); Account dst = em.find(Account.class, to); src.debit(amount); dst.credit(amount); tx.commit(); } catch (Exception e) { tx.rollback(); throw e; } }
Moderne @ApplicationScoped public class AccountService { @PersistenceContext EntityManager em; @Transactional public void transferFunds(Long from, Long to, BigDecimal amount) { var src = em.find(Account.class, from); var dst = em.find(Account.class, to); src.debit(amount); dst.credit(amount); } }
survolez pour voir le moderne →
Enterprise

Message-Driven Bean vs Reactive Messaging

Ancien @MessageDriven(activationConfig = { @ActivationConfigProperty( propertyName = "destinationType", propertyValue = "jakarta.jms.Queue"), @ActivationConfigProperty( propertyName = "destination", propertyValue = "java:/jms/OrderQueue") }) public class OrderMDB implements MessageListener { @Override public void onMessage(Message message) { TextMessage txt = (TextMessage) message; processOrder(txt.getText()); } }
Moderne @ApplicationScoped public class OrderProcessor { @Incoming("orders") public void process(Order order) { // automatically deserialized from // the "orders" channel fulfillOrder(order); } }
survolez pour voir le moderne →
Enterprise

Servlet versus JAX-RS

Ancien @WebServlet("/users") public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String id = req.getParameter("id"); res.setContentType("application/json"); res.getWriter().write("{\"id\":\"" + id + "\"}"); } }
Moderne @Path("/users") public class UserResource { @GET @Produces(MediaType.APPLICATION_JSON) public Response getUser( @QueryParam("id") String id) { return Response.ok(new User(id)).build(); } }
survolez pour voir le moderne →
Enterprise

Singleton EJB vs CDI @ApplicationScoped

Ancien @Singleton @Startup @ConcurrencyManagement( ConcurrencyManagementType.CONTAINER) public class ConfigCache { private Map<String, String> cache; @PostConstruct public void load() { cache = loadFromDatabase(); } @Lock(LockType.READ) public String get(String key) { return cache.get(key); } @Lock(LockType.WRITE) public void refresh() { cache = loadFromDatabase(); } }
Moderne @ApplicationScoped public class ConfigCache { private volatile Map<String, String> cache; @PostConstruct public void load() { cache = loadFromDatabase(); } public String get(String key) { return cache.get(key); } public void refresh() { cache = loadFromDatabase(); } }
survolez pour voir le moderne →
Enterprise

Services web SOAP vs Jakarta REST

Ancien @WebService public class UserWebService { @WebMethod public UserResponse getUser( @WebParam(name = "id") String id) { User user = findUser(id); UserResponse res = new UserResponse(); res.setId(user.getId()); res.setName(user.getName()); return res; } }
Moderne @Path("/users") @Produces(MediaType.APPLICATION_JSON) public class UserResource { @Inject UserService userService; @GET @Path("/{id}") public User getUser(@PathParam("id") String id) { return userService.findById(id); } }
survolez pour voir le moderne →
Enterprise

Versionnage d'API dans Spring Framework 7

Ancien // Version 1 controller @RestController @RequestMapping("/api/v1/products") public class ProductControllerV1 { @GetMapping("/{id}") public ProductDtoV1 getProduct( @PathVariable Long id) { return service.getV1(id); } } // Version 2 — duplicated structure @RestController @RequestMapping("/api/v2/products") public class ProductControllerV2 { @GetMapping("/{id}") public ProductDtoV2 getProduct( @PathVariable Long id) { return service.getV2(id); } }
Moderne // Configure versioning once @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configureApiVersioning( ApiVersionConfigurer config) { config.useRequestHeader("X-API-Version"); } } // Single controller, version per method @RestController @RequestMapping("/api/products") public class ProductController { @GetMapping(value = "/{id}", version = "1") public ProductDtoV1 getV1(@PathVariable Long id) { return service.getV1(id); } @GetMapping(value = "/{id}", version = "2") public ProductDtoV2 getV2(@PathVariable Long id) { return service.getV2(id); } }
survolez pour voir le moderne →
Enterprise

Sécurité contre les nuls dans Spring avec JSpecify

Ancien import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; public class UserService { @Nullable public User findById(@NonNull String id) { return repository.findById(id).orElse(null); } @NonNull public List<User> findAll() { return repository.findAll(); } @NonNull public User save(@NonNull User user) { return repository.save(user); } }
Moderne import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @NullMarked public class UserService { public @Nullable User findById(String id) { return repository.findById(id).orElse(null); } public List<User> findAll() { return repository.findAll(); } public User save(User user) { return repository.save(user); } }
survolez pour voir le moderne →
Enterprise

Configuration XML de Spring vs basée sur annotations

Ancien <!-- applicationContext.xml --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userRepository" class="com.example.UserRepository"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="userService" class="com.example.UserService"> <property name="repository" ref="userRepository"/> </bean> </beans>
Moderne @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } @Repository public class UserRepository { private final JdbcTemplate jdbc; public UserRepository(JdbcTemplate jdbc) { this.jdbc = jdbc; } } @Service public class UserService { private final UserRepository repository; public UserService(UserRepository repository) { this.repository = repository; } }
survolez pour voir le moderne →
112
Patterns Modernes
17
Versions JDK Couvertes
10
Catégories
0
Lignes de Python Requises
ESC