diff --git a/references/collections.md b/references/collections.md index 997e86d..21266a8 100644 --- a/references/collections.md +++ b/references/collections.md @@ -1,5 +1,37 @@ # Collections Patterns +## Collection bulk operations instead of mutation loops +- **Since:** Java 8 +- **Old approach:** Manual iterator removal (Iterator mutation loop) +- **Modern approach:** Collection.removeIf() (Java 8+) +- **Summary:** Use removeIf(), replaceAll(), and List.sort() for direct collection transformations. + +### Before +```java +for (Iterator it = orders.iterator(); it.hasNext();) { + if (it.next().cancelled()) { + it.remove(); + } +} +``` + +### After +```java +orders.removeIf(Order::cancelled); +``` + +### Why modern wins +- **Clear intent:** The operation describes what changes, not how to iterate. +- **Safer mutation:** Avoids manual iterator-removal rules. +- **Less code:** Common transformations become a single expression. + +### References +- [Collection.removeIf()](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Collection.html#removeIf(java.util.function.Predicate)) +- [List.replaceAll()](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/List.html#replaceAll(java.util.function.UnaryOperator)) +- [List.sort()](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/List.html#sort(java.util.Comparator)) + +--- + ## Collectors.teeing() - **Since:** Java 12 - **Old approach:** Two Passes (Java 8) @@ -36,6 +68,40 @@ var result = items.stream().collect( --- +## Comparator factories and fluent ordering +- **Since:** Java 8 +- **Old approach:** Anonymous Comparator (Anonymous comparator) +- **Modern approach:** Comparator factories (Java 8+) +- **Summary:** Build readable, composable ordering rules with Comparator factory methods. + +### Before +```java +people.sort(new Comparator() { + @Override + public int compare(Person a, Person b) { + int byName = a.name().compareTo(b.name()); + return byName != 0 ? byName : Integer.compare(a.age(), b.age()); + } +}); +``` + +### After +```java +people.sort(Comparator.comparing(Person::name) + .thenComparingInt(Person::age)); +``` + +### Why modern wins +- **Composable:** Ordering rules chain naturally. +- **Easier to review:** Sort keys and priority are explicit. +- **Safer comparisons:** Specialized helpers avoid error-prone arithmetic comparators. + +### References +- [Comparator.comparing()](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Comparator.html#comparing(java.util.function.Function)) +- [Comparator.thenComparingInt()](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Comparator.html#thenComparingInt(java.util.function.ToIntFunction)) + +--- + ## Copying collections immutably - **Since:** Java 10 - **Old approach:** Manual Copy + Wrap (Java 8) @@ -164,6 +230,69 @@ Set set = --- +## Legacy synchronized collections to modern alternatives +- **Since:** Java 5 +- **Old approach:** Hashtable (Legacy synchronized collection) +- **Modern approach:** ConcurrentHashMap (Concurrent Collections API) +- **Summary:** Replace Vector and Hashtable with collections selected for actual mutability and concurrency needs. + +### Before +```java +Hashtable sessions = new Hashtable<>(); +sessions.put(id, session); +``` + +### After +```java +ConcurrentMap sessions = new ConcurrentHashMap<>(); +sessions.put(id, session); +``` + +### Why modern wins +- **Intentional concurrency:** The chosen type documents whether sharing is expected. +- **Better scalability:** ConcurrentHashMap avoids a single table-wide lock for normal access. +- **Modern APIs:** Works naturally with current collection and concurrent-map operations. + +### References +- [ConcurrentHashMap](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/ConcurrentHashMap.html) +- [ConcurrentMap](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/ConcurrentMap.html) + +--- + +## Atomic map updates with compute and merge +- **Since:** Java 8 +- **Old approach:** get() + null check + put() (Manual lookup and update) +- **Modern approach:** computeIfAbsent() (Java 8+) +- **Summary:** Replace multi-step map lookup and mutation with computeIfAbsent(), compute(), and merge(). + +### Before +```java +List values = groups.get(key); +if (values == null) { + values = new ArrayList<>(); + groups.put(key, values); +} +values.add(value); +``` + +### After +```java +groups.computeIfAbsent(key, ignored -> new ArrayList<>()) + .add(value); +``` + +### Why modern wins +- **Fewer lookups:** Avoids repeated key access. +- **Better concurrency semantics:** Concurrent maps can perform supported updates atomically. +- **Clear intent:** The initialization rule appears beside the access. + +### References +- [Map.computeIfAbsent()](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Map.html#computeIfAbsent(K,java.util.function.Function)) +- [Map.compute()](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Map.html#compute(K,java.util.function.BiFunction)) +- [Map.merge()](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Map.html#merge(K,V,java.util.function.BiFunction)) + +--- + ## Map.entry() factory - **Since:** Java 9 - **Old approach:** SimpleEntry (Java 8) @@ -260,6 +389,38 @@ var reversed = list.reversed(); --- +## Stack to Deque and ArrayDeque +- **Since:** Java 6 +- **Old approach:** Stack (Legacy Stack) +- **Modern approach:** Deque with ArrayDeque (Deque API) +- **Summary:** Use the Deque interface with ArrayDeque instead of the legacy Stack class. + +### Before +```java +Stack stack = new Stack<>(); +stack.push("task"); +String next = stack.pop(); +``` + +### After +```java +Deque stack = new ArrayDeque<>(); +stack.push("task"); +String next = stack.pop(); +``` + +### Why modern wins +- **Better abstraction:** Deque explicitly models both stack and queue operations. +- **Lower overhead:** ArrayDeque avoids Vector's legacy synchronization. +- **More flexible:** The same interface supports LIFO and FIFO algorithms. + +### References +- [Deque (Java 25)](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Deque.html) +- [ArrayDeque (Java 25)](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/ArrayDeque.html) +- [Stack (Java 25)](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Stack.html) + +--- + ## Typed stream toArray - **Since:** Java 8 - **Old approach:** Manual Filter + Copy (Pre-Streams) diff --git a/references/concurrency.md b/references/concurrency.md index e19ef4d..ca8c8ef 100644 --- a/references/concurrency.md +++ b/references/concurrency.md @@ -350,6 +350,77 @@ Thread.sleep( --- +## Unsafe thread termination to cooperative cancellation +- **Since:** Java 5 +- **Old approach:** Thread.stop() (Forced thread termination) +- **Modern approach:** Future.cancel(true) (Future cancellation) +- **Summary:** Replace Thread.stop() and ad hoc shared flags with interruption-aware task cancellation. + +### Before +```java +Thread worker = new Thread(this::runTask); +worker.start(); + +// May stop the thread while shared state is inconsistent. +worker.stop(); +``` + +### After +```java +Future worker = executor.submit(this::runTask); + +// Requests interruption; the task must cooperate. +worker.cancel(true); +``` + +### Why modern wins +- **Preserves invariants:** Tasks stop only at code paths designed for cancellation. +- **Standard control:** Future represents execution, completion, and cancellation together. +- **Composable:** Interruption works with blocking queues, locks, executors, and virtual threads. + +### References +- [Future](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/Future.html) +- [Thread.stop() deprecation](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/Thread.html#stop()) + +--- + +## Timer tasks to scheduled executors +- **Since:** Java 5 +- **Old approach:** Timer and TimerTask (Timer and TimerTask) +- **Modern approach:** ScheduledExecutorService (ScheduledExecutorService) +- **Summary:** Replace Timer and TimerTask with ScheduledExecutorService for robust scheduled work. + +### Before +```java +Timer timer = new Timer("refresh-timer"); +timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + refresh(); + } +}, 0, 60_000); +``` + +### After +```java +ScheduledExecutorService scheduler = + Executors.newScheduledThreadPool(2); + +scheduler.scheduleAtFixedRate( + this::refresh, 0, 1, TimeUnit.MINUTES); +``` + +### Why modern wins +- **Better isolation:** A configurable thread pool prevents unrelated schedules from sharing one fragile worker. +- **Robust execution:** One failing task does not terminate the scheduler itself. +- **Controllable lifecycle:** Futures, cancellation, delays, and shutdown use standard executor APIs. + +### References +- [ScheduledExecutorService](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/ScheduledExecutorService.html) +- [Executors](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/Executors.html) + +--- + ## Virtual threads - **Since:** Java 21 - **Old approach:** Platform Threads (Java 8) @@ -382,3 +453,39 @@ Thread.startVirtualThread(() -> { - [Thread.ofVirtual()](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/Thread.html#ofVirtual()) --- + +## Wait and notify queues to BlockingQueue +- **Since:** Java 5 +- **Old approach:** Manual wait and notify (Manual wait and notify) +- **Modern approach:** BlockingQueue (BlockingQueue) +- **Summary:** Replace hand-written producer-consumer coordination with BlockingQueue. + +### Before +```java +synchronized (queue) { + while (queue.isEmpty()) { + queue.wait(); + } + Job job = queue.removeFirst(); + process(job); +} +``` + +### After +```java +BlockingQueue queue = new LinkedBlockingQueue<>(); + +Job job = queue.take(); +process(job); +``` + +### Why modern wins +- **Safer coordination:** The queue owns the locking and condition signaling. +- **Clear intent:** put() and take() directly express producer-consumer behavior. +- **Built-in policies:** Choose bounded capacity, fairness, timeouts, or non-blocking operations. + +### References +- [BlockingQueue](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/BlockingQueue.html) +- [LinkedBlockingQueue](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/LinkedBlockingQueue.html) + +--- diff --git a/references/datetime.md b/references/datetime.md index 4d4b0ae..a3b28f2 100644 --- a/references/datetime.md +++ b/references/datetime.md @@ -177,6 +177,34 @@ Instant now = Instant.now(); --- +## Locale constructors to Locale.of() +- **Since:** Java 19 +- **Old approach:** Locale constructors (Deprecated constructor) +- **Modern approach:** Locale.of() (Java 19+) +- **Summary:** Create language and region locales with Locale.of() instead of deprecated constructors. + +### Before +```java +Locale brazilianPortuguese = + new Locale("pt", "BR"); +``` + +### After +```java +Locale brazilianPortuguese = + Locale.of("pt", "BR"); +``` + +### Why modern wins +- **Supported API:** Avoids deprecated constructors. +- **Clear construction:** The named factory communicates intent. +- **Consistent style:** Matches modern JDK value-object factories. + +### References +- [Locale.of()](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Locale.html#of(java.lang.String,java.lang.String)) + +--- + ## Math.clamp() - **Since:** Java 21 - **Old approach:** Nested min/max (Java 8) diff --git a/references/detection-patterns.md b/references/detection-patterns.md index 6ad7d7e..920ddea 100644 --- a/references/detection-patterns.md +++ b/references/detection-patterns.md @@ -7,14 +7,19 @@ _Auto-generated from upstream YAML data. Do not edit manually._ ## Collections +- **collection-bulk-operations** (Java 8+): Old=`Manual iterator removal` | Detect: `.next().cancelled()`, `orders.iterator()`, `it.hasNext()`, `it.next()`, `it.remove()` - **collectors-teeing** (Java 12+): Old=`Two Passes` | Detect: `new Stats(`, `.stream().count()`, `items.stream()` +- **comparator-factories** (Java 8+): Old=`Anonymous Comparator` | Detect: `Integer.compare(`, `new Comparator(`, `@Override`, `b.name())` - **copying-collections-immutably** (Java 10+): Old=`Manual Copy + Wrap` | Detect: `Collections.unmodifiableList(`, `new ArrayList(` - **immutable-list-creation** (Java 9+): Old=`Verbose Wrapping` | Detect: `Collections.unmodifiableList(`, `Arrays.asList(`, `new ArrayList(` - **immutable-map-creation** (Java 9+): Old=`Map Builder Pattern` | Detect: `Collections.unmodifiableMap(`, `new HashMap(` - **immutable-set-creation** (Java 9+): Old=`Verbose Wrapping` | Detect: `Collections.unmodifiableSet(`, `Arrays.asList(`, `new HashSet(` +- **legacy-synchronized-collections** (Java 5+): Old=`Hashtable` | Detect: `new Hashtable(` +- **map-compute-and-merge** (Java 8+): Old=`get() + null check + put()` | Detect: `new ArrayList(`, `groups.get(key)`, `values.add(value)` - **map-entry-factory** (Java 9+): Old=`SimpleEntry` | Detect: `SimpleEntry` - **reverse-list-iteration** (Java 21+): Old=`Manual ListIterator` | Detect: `System.out.println(`, `list.listIterator(list.size())`, `it.hasPrevious()`, `it.previous()`, `out.println(element)` - **sequenced-collections** (Java 21+): Old=`Index Arithmetic` | Detect: `list.get(list.size() - 1)`, `list.get(0)` +- **stack-to-deque** (Java 6+): Old=`Stack` | Detect: `new Stack(`, `stack.pop()` - **stream-toarray-typed** (Java 8+): Old=`Manual Filter + Copy` | Detect: `new ArrayList(`, `n.length()`, `filtered.add(n)` - **unmodifiable-collectors** (Java 16+): Old=`collectingAndThen` | Detect: `Collectors.collectingAndThen(`, `Collectors.toList(` @@ -29,7 +34,10 @@ _Auto-generated from upstream YAML data. Do not edit manually._ - **stable-values** (Java 25+): Old=`Double-Checked Locking` | Detect: `Double-Checked Locking` - **structured-concurrency** (Java 25+): Old=`Manual Thread Lifecycle` | Detect: `Executors.newFixedThreadPool(`, `exec.shutdown()` - **thread-sleep-duration** (Java 19+): Old=`Milliseconds` | Detect: `Thread.sleep(` +- **thread-stop-to-cooperative-cancellation** (Java 5+): Old=`Thread.stop()` | Detect: `new Thread(`, `worker.start()`, `worker.stop()` +- **timer-task-to-scheduled-executor** (Java 5+): Old=`Timer and TimerTask` | Detect: `new Timer(`, `new TimerTask(`, `@Override` - **virtual-threads** (Java 21+): Old=`Platform Threads` | Detect: `System.out.println(`, `new Thread(`, `thread.start()`, `thread.join()` +- **wait-notify-to-blocking-queue** (Java 5+): Old=`Manual wait and notify` | Detect: `queue.isEmpty())`, `queue.wait()`, `queue.removeFirst()` ## Datetime @@ -38,6 +46,7 @@ _Auto-generated from upstream YAML data. Do not edit manually._ - **hex-format** (Java 17+): Old=`Manual Hex Conversion` | Detect: `String.format(`, `Integer.parseInt(` - **instant-precision** (Java 9+): Old=`Milliseconds` | Detect: `System.currentTimeMillis(` - **java-time-basics** (Java 8+): Old=`Date + Calendar` | Detect: `Calendar.getInstance(`, `cal.getTime()` +- **locale-of** (Java 19+): Old=`Locale constructors` | Detect: `new Locale(` - **math-clamp** (Java 21+): Old=`Nested min/max` | Detect: `Nested min/max` ## Enterprise @@ -56,6 +65,7 @@ _Auto-generated from upstream YAML data. Do not edit manually._ - **singleton-ejb-vs-cdi-application-scoped** (Java 11+): Old=`@Singleton EJB` | Detect: `@Singleton`, `@Startup`, `@ConcurrencyManagement`, `@PostConstruct`, `@Lock`, `cache.get(key)` - **soap-vs-jakarta-rest** (Java 11+): Old=`JAX-WS / SOAP` | Detect: `new UserResponse(`, `@WebService`, `@WebMethod`, `@WebParam`, `res.setId(user.getId())`, `res.setName(user.getName())` - **spring-api-versioning** (Java 17+): Old=`Manual URL Path Versioning` | Detect: `@RestController`, `@RequestMapping`, `@GetMapping`, `@PathVariable`, `service.getV1(id)`, `service.getV2(id)` +- **spring-boot-mvc-config** (Java 17+): Old=`WebMvcConfigurerAdapter with @EnableWebMvc` | Detect: `@EnableWebMvc`, `@Configuration`, `@Override`, `extends WebMvcConfigurerAdapter` - **spring-null-safety-jspecify** (Java 17+): Old=`Spring @NonNull/@Nullable` | Detect: `@Nullable`, `@NonNull`, `repository.findById(id)`, `repository.findAll()`, `repository.save(user)` - **spring-xml-config-vs-annotations** (Java 17+): Old=`XML Bean Definitions` | Detect: `XML Bean Definitions` @@ -72,18 +82,23 @@ _Auto-generated from upstream YAML data. Do not edit manually._ ## Io - **deserialization-filters** (Java 9+): Old=`Accept Everything` | Detect: `new ObjectInputStream(`, `ois.readObject()` +- **explicit-charset-file-io** (Java 7+): Old=`Platform-default charset` | Detect: `new FileReader(`, `path.toFile())` - **file-memory-mapping** (Java 22+): Old=`MappedByteBuffer` | Detect: `FileChannel.open(`, `channel.size())` - **files-mismatch** (Java 12+): Old=`Manual Byte Compare` | Detect: `Files.readAllBytes(`, `Arrays.equals(` +- **finalizers-to-resource-cleanup** (Java 9+): Old=`Override finalize()` | Detect: `Handle.release(`, `@Override`, `nativeHandle.release()` - **http-client** (Java 11+): Old=`HttpURLConnection` | Detect: `new URL(`, `new BufferedReader(`, `new InputStreamReader(`, `url.openConnection()`, `con.getInputStream())` +- **http-websocket-client** (Java 11+): Old=`Third-party WebSocket client` | Detect: `new WebSocketClient(`, `@Override`, `client.connect()` - **inputstream-transferto** (Java 9+): Old=`Manual Copy Loop` | Detect: `input.read(buf))` - **io-class-console-io** (Java 25+): Old=`System.out / Scanner` | Detect: `System.out.print(`, `System.out.println(`, `new Scanner(`, `sc.nextLine()`, `sc.close()` - **path-of** (Java 11+): Old=`Paths.get()` | Detect: `Paths.get(` - **reading-files** (Java 11+): Old=`BufferedReader` | Detect: `new StringBuilder(`, `new BufferedReader(`, `new FileReader(`, `br.readLine())`, `sb.append(line)`, `sb.toString()` - **try-with-resources-effectively-final** (Java 9+): Old=`Re-declare Variable` | Detect: `Re-declare Variable` +- **url-constructors-to-uri** (Java 20+): Old=`Direct URL construction` | Detect: `new URL(` - **writing-files** (Java 11+): Old=`FileWriter + BufferedWriter` | Detect: `new FileWriter(`, `new BufferedWriter(`, `bw.write(content)` ## Language +- **anonymous-classes-to-lambdas** (Java 8+): Old=`Anonymous class` | Detect: `new ActionListener(`, `@Override` - **call-c-from-java** (Java 22+): Old=`JNI (Java Native Interface)` | Detect: `System.loadLibrary(`, `System.out.println(` - **compact-canonical-constructor** (Java 16+): Old=`Explicit constructor validation` | Detect: `Objects.requireNonNull(`, `List.copyOf(` - **compact-source-files** (Java 25+): Old=`Main Class Ceremony` | Detect: `System.out.println(` @@ -98,6 +113,7 @@ _Auto-generated from upstream YAML data. Do not edit manually._ - **pattern-matching-switch** (Java 21+): Old=`if-else Chain` | Detect: `if-else Chain` - **primitive-types-in-patterns** (Java 25+): Old=`Manual Range Checks` | Detect: `Manual Range Checks` - **private-interface-methods** (Java 9+): Old=`Duplicated Logic` | Detect: `System.out.println(` +- **raw-collections-to-generics** (Java 5+): Old=`Raw collections` | Detect: `new ArrayList(`, `names.get(0)` - **record-patterns** (Java 21+): Old=`Manual Access` | Detect: `System.out.println(` - **records-for-data-classes** (Java 16+): Old=`Verbose POJO` | Detect: `Verbose POJO` - **sealed-classes** (Java 17+): Old=`Open Hierarchy` | Detect: `extends Shape` @@ -113,6 +129,8 @@ _Auto-generated from upstream YAML data. Do not edit manually._ - **key-derivation-functions** (Java 25+): Old=`Manual PBKDF2` | Detect: `SecretKeyFactory.getInstance(`, `new PBEKeySpec(`, `factory.generateSecret(spec)` - **pem-encoding** (Java 25+): Old=`Manual Base64 + Headers` | Detect: `Base64.getMimeEncoder(`, `cert.getEncoded())` - **random-generator** (Java 17+): Old=`new Random() / ThreadLocalRandom` | Detect: `ThreadLocalRandom.current(`, `new Random(`, `rng.nextInt(100)` +- **security-manager-migration** (Java 24+): Old=`SecurityManager checks` | Detect: `System.getSecurityManager(`, `Files.readString(`, `manager.checkRead(path.toString())` +- **standard-base64** (Java 8+): Old=`sun.misc encoder` | Detect: `misc.BASE64Encoder()` - **strong-random** (Java 9+): Old=`new SecureRandom()` | Detect: `new SecureRandom(`, `random.nextBytes(bytes)` - **tls-default** (Java 11+): Old=`Manual TLS Config` | Detect: `SSLContext.getInstance(`, `ctx.getSocketFactory()` @@ -144,9 +162,13 @@ _Auto-generated from upstream YAML data. Do not edit manually._ - **aot-class-preloading** (Java 25+): Old=`Cold Start Every Time` | Detect: `Cold Start Every Time` - **built-in-http-server** (Java 18+): Old=`External Server / Framework` | Detect: `HttpServer.create(`, `new InetSocketAddress(`, `server.start()` +- **class-file-api** (Java 24+): Old=`ASM ClassReader` | Detect: `Files.readAllBytes(`, `new ClassReader(` +- **class-newinstance-to-constructor** (Java 9+): Old=`Class.newInstance()` | Detect: `Class.newInstance(`, `pluginClass.newInstance()` - **compact-object-headers** (Java 25+): Old=`128-bit Headers` | Detect: `128-bit Headers` - **jfr-profiling** (Java 9+): Old=`External Profiler` | Detect: `External Profiler` - **jshell-prototyping** (Java 9+): Old=`Create File + Compile + Run` | Detect: `Create File + Compile + Run` - **junit6-with-jspecify** (Java 17+): Old=`Unannotated API` | Detect: `@Test`, `result.name())` - **multi-file-source** (Java 22+): Old=`Compile All First` | Detect: `Compile All First` +- **runtime-exec-to-process-builder** (Java 5+): Old=`Runtime.exec(String)` | Detect: `Runtime.getRuntime(` - **single-file-execution** (Java 11+): Old=`Two-Step Compile` | Detect: `Two-Step Compile` +- **stack-walker** (Java 9+): Old=`Thread.getStackTrace()` | Detect: `Thread.currentThread(`, `caller.getClassName()` diff --git a/references/enterprise.md b/references/enterprise.md index e3e60f8..226f91c 100644 --- a/references/enterprise.md +++ b/references/enterprise.md @@ -751,6 +751,48 @@ public class ProductController { --- +## Spring Boot MVC Configuration +- **Since:** Java 17 +- **Old approach:** WebMvcConfigurerAdapter with @EnableWebMvc (Spring Boot 1.x) +- **Modern approach:** WebMvcConfigurer with Spring Boot Auto-Configuration (Spring Boot 4.x) +- **Summary:** Replace the removed WebMvcConfigurerAdapter with WebMvcConfigurer and retain Spring Boot's MVC auto-configuration. + +### Before +```java +@EnableWebMvc +@Configuration +public class WebConfig extends WebMvcConfigurerAdapter { + @Override + public void addViewControllers( + ViewControllerRegistry registry) { + registry.addViewController("/").setViewName("home"); + } +} +``` + +### After +```java +@Configuration +public class WebConfig implements WebMvcConfigurer { + @Override + public void addViewControllers( + ViewControllerRegistry registry) { + registry.addViewController("/").setViewName("home"); + } +} +``` + +### Why modern wins +- **No removed adapter:** WebMvcConfigurer supplies default methods, so there is no adapter superclass to extend. +- **Keeps Boot defaults:** Without @EnableWebMvc, Spring Boot continues to configure MVC infrastructure automatically. +- **Override only what matters:** Implement the interface and customize just the MVC hook required by the application. + +### References +- [Spring Framework — MVC Java Configuration](https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-config.html) +- [Spring Boot — MVC Auto-configuration](https://docs.spring.io/spring-boot/reference/web/servlet.html) + +--- + ## Spring Null Safety with JSpecify - **Since:** Java 17 - **Old approach:** Spring @NonNull/@Nullable (Spring 5/6) diff --git a/references/io.md b/references/io.md index e5d0a75..681e799 100644 --- a/references/io.md +++ b/references/io.md @@ -37,6 +37,38 @@ Object obj = ois.readObject(); --- +## Default-charset file I/O to explicit StandardCharsets +- **Since:** Java 7 +- **Old approach:** Platform-default charset (Platform-default charset) +- **Modern approach:** Explicit standard charset (Explicit standard charset) +- **Summary:** Use Files readers and writers with StandardCharsets constants instead of platform-default charset behavior and charset-name lookup. + +### Before +```java +try (Reader reader = new FileReader(path.toFile())) { + return readAll(reader); +} +``` + +### After +```java +try (BufferedReader reader = Files.newBufferedReader( + path, StandardCharsets.UTF_8)) { + return readAll(reader); +} +``` + +### Why modern wins +- **Portable results:** The same bytes decode identically on every machine. +- **Predefined constant:** StandardCharsets.UTF_8 cannot contain a misspelled charset name. +- **Modern file API:** Path, buffering, and encoding are expressed in one operation. + +### References +- [Files.newBufferedReader(Path, Charset)](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/nio/file/Files.html#newBufferedReader(java.nio.file.Path,java.nio.charset.Charset)) +- [StandardCharsets](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/nio/charset/StandardCharsets.html) + +--- + ## File memory mapping - **Since:** Java 22 - **Old approach:** MappedByteBuffer (Java 8) @@ -118,6 +150,63 @@ long pos = Files.mismatch(path1, path2); --- +## Finalizers to deterministic resource cleanup +- **Since:** Java 9 +- **Old approach:** Override finalize() (Finalization) +- **Modern approach:** AutoCloseable and Cleaner (Java 9+) +- **Summary:** Replace finalization with AutoCloseable and use Cleaner only as a defensive fallback. + +### Before +```java +@Override +protected void finalize() throws Throwable { + nativeHandle.release(); +} +``` + +### After +```java +final class NativeResource implements AutoCloseable { + private static final Cleaner CLEANER = Cleaner.create(); + + private static final class State implements Runnable { + private final long handle; + + State(long handle) { + this.handle = handle; + } + + @Override + public void run() { + release(handle); + } + } + + private final Cleaner.Cleanable cleanable; + + NativeResource(long handle) { + cleanable = CLEANER.register(this, new State(handle)); + } + + @Override + public void close() { + cleanable.clean(); + } +} +``` + +### Why modern wins +- **Deterministic:** Try-with-resources releases resources at a known point. +- **Safer lifecycle:** Avoids finalizer resurrection and unpredictable ordering. +- **Defensive fallback:** Cleaner can recover leaked resources without overriding finalize(). + +### References +- [Cleaner](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/ref/Cleaner.html) +- [AutoCloseable](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/AutoCloseable.html) +- [JEP 421: Deprecate Finalization for Removal](https://openjdk.org/jeps/421) + +--- + ## Modern HTTP client - **Since:** Java 11 - **Old approach:** HttpURLConnection (Java 8) @@ -157,6 +246,53 @@ String body = response.body(); --- +## WebSocket clients with java.net.http +- **Since:** Java 11 +- **Old approach:** Third-party WebSocket client (Third-party library) +- **Modern approach:** java.net.http.WebSocket (Java 11+) +- **Summary:** Use the standard asynchronous WebSocket client instead of adding a third-party dependency. + +### Before +```java +WebSocketClient client = + new WebSocketClient(serverUri) { + @Override + public void onMessage(String message) { + handle(message); + } + }; +client.connect(); +``` + +### After +```java +HttpClient.newHttpClient() + .newWebSocketBuilder() + .buildAsync(serverUri, + new WebSocket.Listener() { + @Override + public CompletionStage onText( + WebSocket socket, + CharSequence data, + boolean last) { + handle(data.toString()); + return WebSocket.Listener.super + .onText(socket, data, last); + } + }); +``` + +### Why modern wins +- **No extra dependency:** The WebSocket client ships with the JDK. +- **Asynchronous API:** Connection and message handling compose with CompletionStage. +- **Shared HTTP stack:** Proxy, TLS, and executor configuration use standard JDK facilities. + +### References +- [WebSocket](https://docs.oracle.com/en/java/javase/25/docs/api/java.net.http/java/net/http/WebSocket.html) +- [HTTP Client (JEP 321)](https://openjdk.org/jeps/321) + +--- + ## InputStream.transferTo() - **Since:** Java 9 - **Old approach:** Manual Copy Loop (Java 8) @@ -216,7 +352,7 @@ IO.println("Hello, " + name); - **Beginner-friendly:** New developers can do console I/O without learning Scanner, System.out, or\ import statements." ### References -- [Simple Source Files (JEP 495)](https://openjdk.org/jeps/495) +- [Compact Source Files and Instance Main Methods (JEP 512)](https://openjdk.org/jeps/512) - [IO](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/IO.html) --- @@ -319,6 +455,36 @@ try (conn) { --- +## Deprecated URL constructors to URI +- **Since:** Java 20 +- **Old approach:** Direct URL construction (Deprecated URL constructor) +- **Modern approach:** URI then URL (Java 20+) +- **Summary:** Parse and compose resource identifiers as URI values before converting to URL\ when necessary. + +### Before +```java +URL endpoint = + new URL("https://example.com/api?q=java"); +``` + +### After +```java +URI endpointUri = + URI.create("https://example.com/api?q=java"); +URL endpoint = endpointUri.toURL(); +``` + +### Why modern wins +- **Avoids deprecation:** Removes use of deprecated URL constructors. +- **Predictable semantics:** URI comparison does not perform network-dependent resolution. +- **Better composition:** URI is designed for parsing and manipulating identifiers. + +### References +- [URI](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/net/URI.html) +- [URL](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/net/URL.html) + +--- + ## Writing files - **Since:** Java 11 - **Old approach:** FileWriter + BufferedWriter (Java 8) diff --git a/references/language.md b/references/language.md index d766519..ddb0b86 100644 --- a/references/language.md +++ b/references/language.md @@ -1,5 +1,37 @@ # Language Patterns +## Anonymous classes to lambdas and method references +- **Since:** Java 8 +- **Old approach:** Anonymous class (Anonymous class) +- **Modern approach:** Method reference (Java 8+) +- **Summary:** Replace single-method anonymous classes with concise lambdas and method references. + +### Before +```java +button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + save(event); + } +}); +``` + +### After +```java +button.addActionListener(this::save); +``` + +### Why modern wins +- **Less boilerplate:** Removes the class declaration and override ceremony. +- **Better readability:** Keeps attention on the operation being performed. +- **Still type-safe:** The compiler checks the target functional-interface signature. + +### References +- [Lambda Expressions](https://dev.java/learn/lambdas/) +- [Method References](https://dev.java/learn/lambdas/method-references/) + +--- + ## Calling out to C code from Java - **Since:** Java 22 - **Old approach:** JNI (Java Native Interface) (Java 1.1+) @@ -589,6 +621,38 @@ interface Logger { --- +## Raw collections to generic types +- **Since:** Java 5 +- **Old approach:** Raw collections (Java 1.4) +- **Modern approach:** Generic collections (Java 5+) +- **Summary:** Replace raw collection types and retrieval casts with compile-time generic type safety. + +### Before +```java +List names = new ArrayList(); +names.add("Duke"); + +String name = (String) names.get(0); +``` + +### After +```java +List names = new ArrayList(); +names.add("Duke"); + +String name = names.get(0); +``` + +### Why modern wins +- **Compile-time safety:** Invalid element types fail during compilation rather than at runtime. +- **No retrieval casts:** Values retain their declared element type. +- **Self-documenting APIs:** Collection declarations state exactly what they contain. + +### References +- [Generics (The Java Tutorials)](https://docs.oracle.com/javase/tutorial/java/generics/index.html) + +--- + ## Record patterns (destructuring) - **Since:** Java 21 - **Old approach:** Manual Access (Java 8) diff --git a/references/pattern-index.md b/references/pattern-index.md index 60cb7ac..f11186f 100644 --- a/references/pattern-index.md +++ b/references/pattern-index.md @@ -1,21 +1,26 @@ # Pattern Index Quick lookup table for all Java modernization patterns. -**Total patterns: 113** +**Total patterns: 135** ## Collections | Slug | Title | JDK Version | Difficulty | |------|-------|-------------|------------| +| collection-bulk-operations | Collection bulk operations instead of mutation loops | 8 | beginner | | collectors-teeing | Collectors.teeing() | 12 | intermediate | +| comparator-factories | Comparator factories and fluent ordering | 8 | beginner | | copying-collections-immutably | Copying collections immutably | 10 | beginner | | immutable-list-creation | Immutable list creation | 9 | beginner | | immutable-map-creation | Immutable map creation | 9 | beginner | | immutable-set-creation | Immutable set creation | 9 | beginner | +| legacy-synchronized-collections | Legacy synchronized collections to modern alternatives | 5 | intermediate | +| map-compute-and-merge | Atomic map updates with compute and merge | 8 | intermediate | | map-entry-factory | Map.entry() factory | 9 | beginner | | reverse-list-iteration | Reverse list iteration | 21 | beginner | | sequenced-collections | Sequenced collections | 21 | beginner | +| stack-to-deque | Stack to Deque and ArrayDeque | 6 | beginner | | stream-toarray-typed | Typed stream toArray | 8 | beginner | | unmodifiable-collectors | Unmodifiable collectors | 16 | intermediate | @@ -32,7 +37,10 @@ Quick lookup table for all Java modernization patterns. | stable-values | Stable values | 25 | advanced | | structured-concurrency | Structured concurrency | 25 | advanced | | thread-sleep-duration | Thread.sleep with Duration | 19 | beginner | +| thread-stop-to-cooperative-cancellation | Unsafe thread termination to cooperative cancellation | 5 | advanced | +| timer-task-to-scheduled-executor | Timer tasks to scheduled executors | 5 | intermediate | | virtual-threads | Virtual threads | 21 | beginner | +| wait-notify-to-blocking-queue | Wait and notify queues to BlockingQueue | 5 | intermediate | ## Datetime @@ -43,6 +51,7 @@ Quick lookup table for all Java modernization patterns. | hex-format | HexFormat | 17 | intermediate | | instant-precision | Instant with nanosecond precision | 9 | intermediate | | java-time-basics | java.time API basics | 8 | beginner | +| locale-of | Locale constructors to Locale.of() | 19 | beginner | | math-clamp | Math.clamp() | 21 | beginner | ## Enterprise @@ -63,6 +72,7 @@ Quick lookup table for all Java modernization patterns. | singleton-ejb-vs-cdi-application-scoped | Singleton EJB vs CDI @ApplicationScoped | 11 | intermediate | | soap-vs-jakarta-rest | SOAP Web Services vs Jakarta REST | 11 | intermediate | | spring-api-versioning | Spring Framework 7 API Versioning | 17 | intermediate | +| spring-boot-mvc-config | Spring Boot MVC Configuration | 17 | beginner | | spring-null-safety-jspecify | Spring Null Safety with JSpecify | 17 | intermediate | | spring-xml-config-vs-annotations | Spring XML Bean Config vs Annotation-Driven | 17 | intermediate | @@ -83,20 +93,25 @@ Quick lookup table for all Java modernization patterns. | Slug | Title | JDK Version | Difficulty | |------|-------|-------------|------------| | deserialization-filters | Deserialization filters | 9 | advanced | +| explicit-charset-file-io | Default-charset file I/O to explicit StandardCharsets | 7 | beginner | | file-memory-mapping | File memory mapping | 22 | advanced | | files-mismatch | Files.mismatch() | 12 | beginner | +| finalizers-to-resource-cleanup | Finalizers to deterministic resource cleanup | 9 | advanced | | http-client | Modern HTTP client | 11 | beginner | +| http-websocket-client | WebSocket clients with java.net.http | 11 | intermediate | | inputstream-transferto | InputStream.transferTo() | 9 | beginner | | io-class-console-io | IO class for console I/O | 25 | beginner | | path-of | Path.of() factory | 11 | beginner | | reading-files | Reading files | 11 | beginner | | try-with-resources-effectively-final | Try-with-resources improvement | 9 | beginner | +| url-constructors-to-uri | Deprecated URL constructors to URI | 20 | intermediate | | writing-files | Writing files | 11 | beginner | ## Language | Slug | Title | JDK Version | Difficulty | |------|-------|-------------|------------| +| anonymous-classes-to-lambdas | Anonymous classes to lambdas and method references | 8 | beginner | | call-c-from-java | Calling out to C code from Java | 22 | advanced | | compact-canonical-constructor | Compact canonical constructor | 16 | intermediate | | compact-source-files | Compact source files | 25 | beginner | @@ -111,6 +126,7 @@ Quick lookup table for all Java modernization patterns. | pattern-matching-switch | Pattern matching in switch | 21 | intermediate | | primitive-types-in-patterns | Primitive types in patterns | 25 | advanced | | private-interface-methods | Private interface methods | 9 | intermediate | +| raw-collections-to-generics | Raw collections to generic types | 5 | beginner | | record-patterns | Record patterns (destructuring) | 21 | intermediate | | records-for-data-classes | Records for data classes | 16 | beginner | | sealed-classes | Sealed classes for type hierarchies | 17 | intermediate | @@ -128,6 +144,8 @@ Quick lookup table for all Java modernization patterns. | key-derivation-functions | Key Derivation Functions | 25 | advanced | | pem-encoding | PEM encoding/decoding | 25 | advanced | | random-generator | RandomGenerator interface | 17 | intermediate | +| security-manager-migration | SecurityManager checks to explicit authorization | 24 | advanced | +| standard-base64 | Standard Base64 encoding and decoding | 8 | beginner | | strong-random | Strong random generation | 9 | beginner | | tls-default | TLS 1.3 by default | 11 | intermediate | @@ -165,9 +183,13 @@ Quick lookup table for all Java modernization patterns. |------|-------|-------------|------------| | aot-class-preloading | AOT class preloading | 25 | advanced | | built-in-http-server | Built-in HTTP server | 18 | beginner | +| class-file-api | Class-file parsing with the standard API | 24 | advanced | +| class-newinstance-to-constructor | Class.newInstance() to constructor reflection | 9 | intermediate | | compact-object-headers | Compact object headers | 25 | advanced | | jfr-profiling | JFR for profiling | 9 | intermediate | | jshell-prototyping | JShell for prototyping | 9 | beginner | | junit6-with-jspecify | JUnit 6 with JSpecify null safety | 17 | intermediate | | multi-file-source | Multi-file source launcher | 22 | intermediate | +| runtime-exec-to-process-builder | Runtime.exec(String) to ProcessBuilder arguments | 5 | intermediate | | single-file-execution | Single-file execution | 11 | beginner | +| stack-walker | Lazy stack inspection with StackWalker | 9 | intermediate | diff --git a/references/security.md b/references/security.md index e6b6205..983521d 100644 --- a/references/security.md +++ b/references/security.md @@ -115,6 +115,74 @@ var rng = RandomGeneratorFactory --- +## SecurityManager checks to explicit authorization +- **Since:** Java 24 +- **Old approach:** SecurityManager checks (JDK 23 and earlier) +- **Modern approach:** Explicit authorization (JDK 24+) +- **Summary:** Replace disabled SecurityManager checks with explicit application authorization\ and deployment isolation. + +### Before +```java +SecurityManager manager = System.getSecurityManager(); +if (manager != null) { + manager.checkRead(path.toString()); +} +return Files.readString(path); +``` + +### After +```java +// Untrusted users must not be able to modify this tree +Path root = allowedRoot.toRealPath(); +Path resolved = root.resolve(requested) + .normalize() + .toRealPath(); +if (!resolved.startsWith(root)) { + throw new SecurityException( + "Path is outside the allowed root"); +} +return Files.readString(resolved); +``` + +### Why modern wins +- **Explicit policy:** Authorization is visible and testable in application logic. +- **Real isolation:** Process, container, and operating-system boundaries protect the whole application. +- **Required migration:** Removes checks that can no longer enforce policy on JDK 24 and later. + +### References +- [Permanently Disable the Security Manager (JEP 486)](https://openjdk.org/jeps/486) +- [SecureDirectoryStream](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/nio/file/SecureDirectoryStream.html) + +--- + +## Standard Base64 encoding and decoding +- **Since:** Java 8 +- **Old approach:** sun.misc encoder (Internal JDK API) +- **Modern approach:** Standard Base64 API (Java 8+) +- **Summary:** Use java.util.Base64 instead of internal JDK classes or third-party codecs. + +### Before +```java +String encoded = new sun.misc.BASE64Encoder() + .encode(data); +``` + +### After +```java +String encoded = Base64.getEncoder() + .encodeToString(data); +``` + +### Why modern wins +- **Supported API:** Avoids inaccessible sun.misc implementation classes. +- **Complete variants:** Includes basic, URL-safe, and MIME encoders and decoders. +- **No dependency:** Encoding and decoding are built into the JDK. + +### References +- [Base64](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Base64.html) + +--- + ## Strong random generation - **Since:** Java 9 - **Old approach:** new SecureRandom() (Java 8) diff --git a/references/tooling.md b/references/tooling.md index 18b6227..46cd5a1 100644 --- a/references/tooling.md +++ b/references/tooling.md @@ -79,6 +79,67 @@ server.start(); --- +## Class-file parsing with the standard API +- **Since:** Java 24 +- **Old approach:** ASM ClassReader (Third-party bytecode parser) +- **Modern approach:** Class-File API (Java 24+) +- **Summary:** Use the standard Class-File API for class-file parsing and transformation. + +### Before +```java +ClassReader reader = new ClassReader( + Files.readAllBytes(classFile)); +reader.accept(visitor, 0); +``` + +### After +```java +ClassModel model = ClassFile.of().parse(classFile); +model.methods().forEach(method -> + System.out.println( + method.methodName().stringValue())); +``` + +### Why modern wins +- **Supported API:** Ships and evolves with the JDK class-file format. +- **Fewer dependencies:** Handles common bytecode tasks without an external library. +- **Composable models:** Parsing, building, and transformation share one API. + +### References +- [ClassFile](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/classfile/ClassFile.html) +- [Class-File API (JEP 484)](https://openjdk.org/jeps/484) + +--- + +## Class.newInstance() to constructor reflection +- **Since:** Java 9 +- **Old approach:** Class.newInstance() (Deprecated Class.newInstance()) +- **Modern approach:** Explicit constructor lookup (Constructor reflection) +- **Summary:** Replace deprecated Class.newInstance() with explicit constructor lookup and invocation. + +### Before +```java +Plugin plugin = pluginClass.newInstance(); +``` + +### After +```java +Plugin plugin = pluginClass + .getDeclaredConstructor() + .newInstance(); +``` + +### Why modern wins +- **Supported API:** Removes use of deprecated Class.newInstance(). +- **Explicit constructor:** The requested signature is visible and can accept arguments. +- **Honest failures:** Constructor exceptions are represented through InvocationTargetException. + +### References +- [Class.getDeclaredConstructor()](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/Class.html#getDeclaredConstructor(java.lang.Class...)) +- [Constructor.newInstance()](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/reflect/Constructor.html#newInstance(java.lang.Object...)) + +--- + ## Compact object headers - **Since:** Java 25 - **Old approach:** 128-bit Headers (Java 8) @@ -290,6 +351,37 @@ $ java Main.java --- +## Runtime.exec(String) to ProcessBuilder arguments +- **Since:** Java 5 +- **Old approach:** Runtime.exec(String) (Command string) +- **Modern approach:** ProcessBuilder (ProcessBuilder) +- **Summary:** Launch processes with an explicit argument list and ProcessBuilder configuration. + +### Before +```java +Process process = Runtime.getRuntime() + .exec("git show " + revision); +``` + +### After +```java +Process process = new ProcessBuilder( + "git", "show", revision) + .redirectErrorStream(true) + .start(); +``` + +### Why modern wins +- **Exact arguments:** Each process argument remains a distinct value without command-string tokenization. +- **Explicit configuration:** Environment, directory, redirects, and error handling are configured together. +- **Safer boundaries:** Avoids constructing a shell-like command string from dynamic values. + +### References +- [ProcessBuilder](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/ProcessBuilder.html) +- [Runtime.exec(String)](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/Runtime.html#exec(java.lang.String)) + +--- + ## Single-file execution - **Since:** Java 11 - **Old approach:** Two-Step Compile (Java 8) @@ -320,3 +412,36 @@ $ java HelloWorld.java - [Launch Single-File Source-Code Programs (JEP 330)](https://openjdk.org/jeps/330) --- + +## Lazy stack inspection with StackWalker +- **Since:** Java 9 +- **Old approach:** Thread.getStackTrace() (Materialized stack trace) +- **Modern approach:** StackWalker (Java 9+) +- **Summary:** Inspect stack frames lazily with StackWalker instead of materializing a complete stack trace. + +### Before +```java +StackTraceElement caller = Thread.currentThread() + .getStackTrace()[2]; +String callerClass = caller.getClassName(); +``` + +### After +```java +String callerClass = StackWalker.getInstance() + .walk(frames -> frames.skip(1) + .findFirst() + .orElseThrow() + .getClassName()); +``` + +### Why modern wins +- **Lazy traversal:** Visits only the frames the operation needs. +- **Stream-friendly:** Filtering and selection use standard stream operations. +- **Configurable:** Options support class references and hidden or reflective frames. + +### References +- [StackWalker](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/StackWalker.html) +- [Stack-Walking API (JEP 259)](https://openjdk.org/jeps/259) + +---