Migration Overview Migrating from Java 8 to Java 11 is the most common Java upgrade scenario. The migration has two distinct phases: Make it compile and run — fix incompatibilities introduced by Java 9–11 Modernise the code — adopt var, List.of(), String.isBlank(), and other new APIs Phase 1 is mandatory and blocks the upgrade. Phase 2 is optional and can happen incrementally. This guide focuses entirely on Phase 1. The 10-Step Migration Checklist [ ] 1.
Continue reading »Java
223 posts in this section
Migrating to Java 17: From Java 8 and Java 11 — Step by Step
Why Migrate to Java 17? Java 17 is the current recommended enterprise LTS. Key reasons to migrate now: Spring Boot 3.x requires Java 17 — the entire Spring ecosystem is moving here Java 11 extended support ends around 2026 depending on your vendor Java 8 mainstream support ended in 2019; extended support ended 2030 for Oracle JDK but active vulnerability exposure is increasing Language features: Records, Sealed Classes, Text Blocks, Pattern Matching, Switch Expressions — all available in Java 17 Security: Strong JDK encapsulation closes years of internal API exposure used in exploits Recommended Migration Path Java 8 → Java 11 → Java 17 Do not jump directly from Java 8 to Java 17 in one step if your codebase is large or has many third-party dependencies.
Continue reading »Migrating to Java 21: From Java 8, 11, and 17 — Step by Step
Why Migrate Now? Java 21 is the current Long-Term Support (LTS) release, and it is the most feature-rich LTS since Java 8. LTS releases receive security patches and bug fixes for years. Java 11, the previous widely-used LTS, reached its extended support window end depending on your vendor. Java 8 mainstream support ended in 2019. More concretely, Java 21 brings: Virtual Threads — drop-in replacement for platform threads, enabling massive concurrency without reactive rewrites Pattern Matching for switch and records — eliminating entire categories of verbose, error-prone instanceof/cast chains Sequenced Collections — a unified API for ordered collection types Generational ZGC — sub-millisecond GC pauses at any heap size These are not incremental improvements.
Continue reading »Module System (JPMS, JEP 261): Project Jigsaw Deep Dive
The Problem JPMS Solves Before Java 9, the JDK had no real notion of encapsulation at the library level. public meant accessible to everyone — including internal JDK classes like sun.misc.Unsafe and com.sun.internal.*. Large codebases suffered from: No reliable encapsulation: Any public class in any JAR was reachable from any other JAR. Classpath hell: Duplicate or conflicting classes from different JARs led to unpredictable behaviour. Monolithic JDK: The entire 60+ module JDK had to ship with every application.
Continue reading »Monitoring: Consumer Lag, Micrometer Metrics, and Actuator Integration
What to Monitor in Kafka Production Kafka applications need visibility into: Consumer lag — how many records are unprocessed per partition Throughput — records produced and consumed per second Error rates — listener exceptions, DLT records, retry counts Producer latency — time from send() to broker acknowledgment Rebalance frequency — high rebalance rate signals consumer instability Dependencies <!-- Micrometer Prometheus registry --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency> <!-- Spring Boot Actuator --> <dependency> <groupId>org.
Continue reading »Multi-Factor Authentication (TOTP and WebAuthn)
Why MFA Matters A password alone is a single point of failure. Phishing, credential stuffing, and password reuse mean that knowing a password is not proof of identity. Multi-factor authentication requires something the user knows (password) and something they have (phone, hardware key) — compromising one factor is no longer sufficient. TOTP: Time-Based One-Time Passwords TOTP (RFC 6238) generates a 6-digit code that changes every 30 seconds, derived from a shared secret and the current time.
Continue reading »Multi-Threaded Steps and Async Processing for Performance
Introduction A single-threaded Spring Batch step processes one chunk at a time — read N items, process N items, write N items, repeat. For large data sets this is a bottleneck. Spring Batch offers two in-JVM scaling options: Approach How it works Use when Multi-threaded step Multiple threads each process independent chunks Reader is thread-safe (JdbcPagingItemReader) AsyncItemProcessor Processing runs concurrently; writes remain sequential I/O-bound processors (REST calls, slow enrichment) This article covers both, plus the thread-safety requirements you must meet.
Continue reading »New APIs: Base64, StringJoiner, Spliterator, Files.lines, StampedLock, Nashorn
Base64 Before Java 8, Base64 encoding required a third-party library (Apache Commons Codec, Guava) or the internal sun.misc.BASE64Encoder class — an API that was never officially supported and was deliberately restricted from Java 9 onwards. Java 8 standardised encoding in java.util.Base64, which covers standard, URL-safe, and MIME variants and can wrap streams for large payloads. Java 8 added java.util.Base64 to the standard library. Three Encoders // Standard Base64 (RFC 4648) Base64.
Continue reading »New String Methods (Java 11): isBlank, lines, strip, repeat
New String Methods in Java 11 Java 11 added six new instance methods to java.lang.String. None require any imports — they are part of the standard String class. Method Returns Description isBlank() boolean True if string is empty or contains only whitespace lines() Stream<String> Stream of lines split by line terminators strip() String Removes leading and trailing Unicode whitespace stripLeading() String Removes leading Unicode whitespace only stripTrailing() String Removes trailing Unicode whitespace only repeat(int n) String Returns the string repeated n times isBlank() Returns true if the string is empty or contains only whitespace characters as defined by Character.
Continue reading »Non-Blocking Retries: @RetryableTopic, BackOff, and the Retry Topic Chain
The Blocking Retry Problem DefaultErrorHandler retries by seeking back to the failed offset. While retrying, no other records from that partition are consumed — the partition is blocked. For a topic with high throughput, one slow retry can cause significant consumer lag. flowchart TD subgraph Blocking["Blocking Retry (DefaultErrorHandler)"] B1["poll() → [r50, r51, r52, r53]"] B2["process r50 ✓"] B3["process r51 ✗ — retry"] B4["wait 10s... retry r51 ✗"] B5["wait 20s... retry r51 ✗"
Continue reading »