Finalized in Java 16 (JEP 395). Available in all Java 16+ releases, including Java 17. Previous previews: Java 14 (JEP 359) and Java 15 (JEP 384). The Problem: Data Classes in Java Writing a simple immutable data class in Java 11 requires significant boilerplate: public final class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } public int x() { return x; } public int y() { return y; } @Override public boolean equals(Object o) { if (this == o) return true; if (!
Continue reading »Tutorial
264 posts in this section
Refresh Tokens and Token Rotation
Why Refresh Tokens? Short-lived access tokens (15–60 minutes) limit the damage if a token is stolen — it expires quickly. But forcing users to log in every hour is terrible UX. Refresh tokens solve this: a long-lived token (7–30 days) stored securely lets the client silently obtain a new access token when the old one expires. The user stays logged in indefinitely without re-entering credentials. sequenceDiagram participant Client participant AuthServer as Auth Endpoint participant API as Protected API Client->>AuthServer: POST /api/auth/login AuthServer-->>Client: {accessToken: exp 15min, refreshToken: exp 7d} Note over Client,API: Normal API usage (15 min) Client->>API: GET /api/data\nAuthorization: Bearer {accessToken} API-->>Client: 200 OK Note over Client,API: Access token expires Client->>API: GET /api/data\nAuthorization: Bearer {expiredToken} API-->>Client: 401 Unauthorized Note over Client,API: Silent token refresh Client->>AuthServer: POST /api/auth/refresh\n{refreshToken} AuthServer-->>Client: {newAccessToken, newRefreshToken} Client->>API: GET /api/data\nAuthorization: Bearer {newAccessToken} API-->>Client: 200 OK Token Rotation: Every Refresh Issues a New Refresh Token Token rotation is the critical security mechanism: every time a refresh token is used, the server issues a new refresh token and invalidates the old one.
Continue reading »Remote Partitioning and Remote Chunking with Kafka
Introduction Local partitioning (Article 21) runs all workers on one JVM. When a single machine is the bottleneck — CPU, memory, or network bandwidth — you need workers on separate machines. Spring Batch Integration provides two patterns for this: Pattern What distributes Coordinator controls Workers do Remote Partitioning Partition descriptors (small messages) Data splitting, aggregation Full read-process-write per partition Remote Chunking Actual items (larger messages) Reading Processing + writing only Remote partitioning is the more common choice — workers read directly from the database/file, so only small partition metadata crosses the network.
Continue reading »Repository Interfaces: CrudRepository, JpaRepository, and How They Work
Introduction Spring Data JPA’s repository abstraction eliminates the DAO boilerplate that every Java application used to require. You declare an interface, extend one of the repository base interfaces, and Spring generates a complete implementation at startup — find, save, delete, count, and more — with zero code written. The Repository Hierarchy Repository<T, ID> ← Marker interface — no methods │ └── CrudRepository<T, ID> ← Basic CRUD: save, find, delete, count │ └── PagingAndSortingRepository<T, ID> ← + findAll(Pageable), findAll(Sort) │ └── JpaRepository<T, ID> ← + flush, saveAndFlush, deleteInBatch Each interface adds more operations.
Continue reading »Request-Reply Pattern with ReplyingKafkaTemplate
When Kafka Needs to Be Synchronous Kafka is designed for asynchronous event streaming. But some flows genuinely need a response: a payment validation service that must confirm before the order proceeds, or a pricing engine that must return the current price before checkout completes. ReplyingKafkaTemplate gives you a blocking send-and-receive call over Kafka without leaving the Kafka ecosystem. How Request-Reply Works sequenceDiagram participant Requester as "Order Service\n(ReplyingKafkaTemplate)" participant Broker participant Replier as "
Continue reading »Retryable vs Non-Retryable Exceptions: Custom Exception Classification
Transient vs Permanent Failures Not every exception is worth retrying. Retrying a NullPointerException or a schema validation error wastes time and delays other records. Retrying a database timeout or a downstream HTTP 503 is exactly right — the error is temporary and will likely resolve. flowchart TD Ex["Exception in listener"] Q{"Transient?\n(DB timeout, HTTP 503,\nnetwork blip)"} Q -->|Yes| Retry["Retry with BackOff"] Q -->|No| Skip["Call recoverer immediately\n(no retries wasted)"] Retry -->|"still failing after\nmax retries"
Continue reading »Role-Based Access Control: Roles, Authorities, and Hierarchies
Roles vs. Authorities: The Distinction That Matters Spring Security uses one interface for both roles and authorities: GrantedAuthority. Both are just strings. The difference is convention. Authority: a fine-grained permission string — user:read, report:export, order:cancel Role: a coarse-grained label that groups authorities — ROLE_ADMIN, ROLE_MANAGER, ROLE_USER The ROLE_ prefix is the only mechanical difference. When you call hasRole("ADMIN"), Spring Security prepends ROLE_ automatically and checks for ROLE_ADMIN. When you call hasAuthority("ROLE_ADMIN") you must include the prefix yourself.
Continue reading »Scheduling Batch Jobs: @Scheduled, Quartz, and Clustered Scheduling
Introduction A batch job that only runs manually is barely useful. Production jobs run on a schedule — nightly, hourly, after a file arrives. Spring Boot provides three scheduling options: Option Persistence Clustered Use when @Scheduled No No Simple cron on a single node Quartz Scheduler Yes (DB) Yes HA scheduling, persistent triggers External scheduler (Kubernetes CronJob, Airflow) Varies Yes Complex pipelines, dependency management @Scheduled — Simple Cron Trigger The simplest approach: annotate a method with @Scheduled and run the job from it.
Continue reading »Scoped Values (JEP 446): Thread-Safe Context Without ThreadLocal
Preview Feature — Requires --enable-preview at compile and runtime. The ThreadLocal Problem at Scale ThreadLocal has been the standard way to pass context (request ID, user session, database transaction) implicitly through a call stack without threading it through every method signature. It works well with a few hundred platform threads. With virtual threads, it breaks down. Problem 1 — Memory overhead with inheritance When you create a child thread with InheritableThreadLocal, Java copies the thread-local map from parent to child:
Continue reading »Sealed Classes (JEP 409): Controlled, Exhaustive Class Hierarchies
Finalized in Java 17 (JEP 409). This is the headline language feature of the Java 17 LTS release. Previous previews: Java 15 (JEP 360) and Java 16 (JEP 397). The Problem: Open Hierarchies Are Hard to Reason About In Java, any class can be extended by default. This openness is flexible but has costs. Consider a Shape interface used in a drawing application: // Java 11 — anyone can implement Shape public interface Shape { double area(); } At runtime, a Shape instance could be a Circle, a Rectangle, a Triangle, or anything else in the classpath.
Continue reading »