Category Archives: Uncategorized

The Java Vector API (JEP 537): SIMD From First Principles to Reference-Grade, with Real Benchmarks

Almost every Java program spends some of its time in a loop that does the same arithmetic to every element of an array — scaling audio samples, normalizing a feature vector, summing a column, comparing pixels. You write that loop one element at a time because that is how a for loop reads. Your CPU, however, has been able to do eight, sixteen, or even sixty-four of those operations in a single instruction for over two decades. The gap between the loop you wrote and the hardware you own is the subject of this post. There are two ways to close that gap in Java. The first is free and invisible: the JIT compiler sometimes rewrites your scalar loop into a vector loop behind your back. The second is explicit and reliable: the Vector API lets you write the vector loop yourself, so it is fast whether or not the compiler would have helped. This guide starts from what a CPU vector even is — assuming you have never thought about it — and ends at reference-grade detail: masks, the tail problem, floating-point reduction hazards, and the API's road out of incubation. Every benchmark here was compiled and run on a live JDK 21; the numbers and output are real, not illustrative.
Status (July 2026): The Vector API is still an incubating feature. JEP 537 proposes its twelfth incubation and is targeted to JDK 27 (due September 2026). It has incubated continuously since JDK 16 (JEP 338) because it is deliberately waiting for Project Valhalla: once value classes arrive as a preview feature, the API will be re-based on them and promoted from incubator to preview. Everything below uses the module jdk.incubator.vector and must be compiled and run with --add-modules jdk.incubator.vector. The shape of the API is stable; the package name will change when it leaves incubation.
Continue reading The Java Vector API (JEP 537): SIMD From First Principles to Reference-Grade, with Real Benchmarks

Java Value Classes (JEP 401): The Complete Guide to Project Valhalla’s First Preview (Beginner to Advanced)

Every Java developer has written a small class whose only job is to hold a few values together — a Point, a Money, an RGB Color. You give it final fields, an equals, a hashCode, and you move on. What you probably never think about is that the JVM quietly staples an extra, invisible property onto every one of those objects: an identity. That identity is why new Color(255,0,0) == new Color(255,0,0) is false even though the two colors are indistinguishable, and it is why an Integer[] is dramatically slower to scan than an int[]. For twenty-five years that cost was simply the price of using classes.

Value classes — the headline feature of Project Valhalla, specified in JEP 401 — let you opt out of that cost. You mark a class with the new value keyword, promise it has no need for identity, and in return the JVM is free to treat its instances like primitives: no allocation, no header, flattened straight into arrays and fields. This post starts from what “object identity” even means (if you have never thought about it, that is fine) and ends at reference-grade detail: scalarization, heap flattening, the changed == semantics, migration rules, and the sharp edges. Each section builds on the one before it, so you can read straight through.

Status (July 2026): Value Classes and Objects is a preview language and VM feature (JEP 401, owned by Dan Smith, reviewed by Brian Goetz). After more than a decade of design, the implementation was integrated into the OpenJDK mainline in July 2026, targeting JDK 28 (due March 2027) as its first preview. Early-access builds are available now at jdk.java.net/valhalla. Everything here must be compiled and run with --enable-preview. The syntax is stable; low-level details may still move between previews.

Continue reading Java Value Classes (JEP 401): The Complete Guide to Project Valhalla’s First Preview (Beginner to Advanced)

Spring Data JPA 3 to 4 Migration Guide: Query Derivation Changes, the hibernate-processor Coordinate Swap, and Value-Objects

When I upgraded a service from Spring Boot 3 to 4, the framework and web-layer changes got all the attention — but the part that actually cost me an afternoon was the repository layer. Spring Boot 4 pulls in the Spring Data 2025.1 generation (Spring Data JPA 4.0), and that carries three changes that don’t show up in a casual read of the release notes: an annotation-processor coordinate that was renamed out from under you, a complete rewrite of how derived queries are generated, and a much friendlier story for modelling value objects. None of these are loud failures. Two of them compile cleanly and then misbehave, which is the worst kind.

This guide walks the whole repository-layer migration beginner to advanced, on a real Spring Boot 4.1.0 project running Spring Data JPA 4.0, Hibernate 7.1, and Java 25. We start with how to confirm which Spring Data generation you’re actually on, then the hibernate-processor coordinate swap and the silent metamodel failure it causes, then the CriteriaQuery-to-JPQL rewrite of derived queries and its one real behavioural edge, then value objects — records as embeddables and as projections — and finally the removals that will fail your build outright. Every error message below is the real one, not a paraphrase.

Continue reading Spring Data JPA 3 to 4 Migration Guide: Query Derivation Changes, the hibernate-processor Coordinate Swap, and Value-Objects

The Foreign Function & Memory API Beyond “Hello, Panama”: Upcalls, errno, and the Coming JNI Lockdown

Most Foreign Function & Memory API tutorials stop at the same place: load a library, call strlen, map a struct, mention jextract, done. That is a fine introduction, but it skips the parts that actually come up once you try to use the FFM API for something real — how do you let native code call back into Java? How do you read a C library’s error code instead of just getting a mysterious -1? What does the JDK 24+ crackdown on unrestricted native access actually mean for your JAR file? This post answers those questions with runnable code and real terminal output, and assumes you already know (or can quickly pick up) the FFM basics — Linker, Arena, MemorySegment, and MemoryLayout — from the JEP itself or any introductory tutorial.

Status (July 2026): The Foreign Function & Memory API (JEP 454) has been a final, standard part of Java SE since JDK 22 (March 2024) — no preview flags required. Every example below was compiled and run on OpenJDK 25 against java.lang.foreign, which has not changed shape since GA; the current release as of this writing is JDK 26 (March 2026). The one moving part is JEP 472, the native-access lockdown, which is still tightening release by release — covered in detail below.

A 60-second recap, on purpose kept brief

If you have never touched java.lang.foreign, here is the whole mental model in five names. Linker is the bridge to the platform ABI and produces MethodHandles for native functions (a “downcall”). SymbolLookup finds a symbol by name in a loaded library. MemorySegment is a bounds-checked view over a region of memory, on- or off-heap. MemoryLayout (and its subtypes StructLayout, SequenceLayout) describes the shape of that memory — field offsets, padding, array strides — so you get VarHandle accessors instead of manual pointer arithmetic. Arena owns the lifetime of the segments it allocates and decides who may close them. If any of those five words are unfamiliar, Oracle’s own Core Libraries Guide chapter on the FFM API is the right place to start before continuing here.

A brief history, and where this fits in Project Panama

The FFM API is one deliverable of the broader Project Panama, which also includes the Vector API for explicit SIMD programming. Panama’s roadmap has moved through eleven-plus preview cycles since JDK 16:

Timeline infographic of Project Panama's JEPs across JDK releases, from early incubation through the Foreign Function and Memory API and Vector API previews
Project Panama’s roadmap across JDK releases. Image credit: Carl Dea, foojay.io — “Project Panama for Newbies (Part 1).”

The FFM half of that roadmap (JEP 454) finished first and went final in JDK 22. The Vector API, its sibling, is still incubating — now in its 11th round as of JDK 26 (JEP 529) — because it is being reworked around Project Valhalla’s value types before finalizing. If your workload needs both native interop and SIMD (image codecs, scientific computing), it is worth knowing they are developed together but ship on different timelines.

Continue reading The Foreign Function & Memory API Beyond “Hello, Panama”: Upcalls, errno, and the Coming JNI Lockdown

double, BigDecimal, or Fixed-Point: Getting Number Precision Right in Java

If you have written Java for any length of time, you have probably been burned by this:

System.out.println(0.1 + 0.2);
0.30000000000000004

That trailing 4 is not a Java bug. It is the single most misunderstood fact in everyday programming, and getting it wrong has cost real companies real money. This guide starts from that one surprising line and walks all the way to production-grade decisions: when plain double is perfectly fine, when BigDecimal is mandatory, and when a humble long is the fastest correct answer of all.

Every code block below was compiled and run on a real JDK, and the output shown is the actual output — not what should happen, but what does.

The bug that looks like magic

Let’s make the abstract concrete. Imagine a checkout that adds ten items at 10 cents each:

double price = 0.10;
double total = 0.0;
for (int i = 0; i < 10; i++) {
    total += price;
}
System.out.println("Total: " + total);
System.out.println("Is it exactly 1.0? " + (total == 1.0));
Total: 0.9999999999999999
Is it exactly 1.0? false

Ten dimes did not add up to a dollar. If a downstream check says if (total == 1.0), it fails. If you round for display but compare on the raw value, your invoice and your ledger disagree by a fraction of a cent — and at scale, fractions of a cent become audit findings.

The fix is not "add a tiny tolerance everywhere." The fix is understanding why this happens, because the why tells you exactly which tool to reach for.

Why it happens: computers count in binary

Humans write numbers in base 10. A double stores them in base 2. The conversion between the two is where precision leaks away.

In base 10, some fractions never terminate — 1/3 is 0.3333… forever. In base 2, far more fractions fail to terminate, and 0.1 is one of them. The number 0.1 simply has no exact finite binary representation, the same way 1/3 has no exact finite decimal one. The CPU stores the closest 64-bit value it can, and that value is not quite 0.1.

You can see the truth with BigDecimal, which can print the exact value a double is holding:

import java.math.BigDecimal;

System.out.println(new BigDecimal(0.1));
0.1000000000000000055511151231257827021181583404541015625

That long tail is what double actually stored when you typed 0.1. Every arithmetic operation carries that tiny error forward, and occasionally — like with ten dimes — the errors line up and become visible.

What a double looks like inside

A Java double is an IEEE 754 64-bit number with three parts: a sign bit, an 11-bit exponent, and a 52-bit fraction (the mantissa). Those 52 fraction bits are the entire budget of precision — roughly 15–17 significant decimal digits. Anything that needs more, or needs exactness in base 10, will not fit.

IEEE 754 double-precision 64-bit layout showing 1 sign bit, 11 exponent bits, and 52 fraction bits
IEEE 754 double-precision layout: 1 sign bit, 11 exponent bits, and 52 fraction (mantissa) bits. Diagram: Codekaizen, Wikimedia Commons, CC BY-SA 4.0.

The key takeaway from that diagram: precision is finite and fixed. A double is a base-2 approximation living in 64 bits, so base-10 values like 0.1, 0.2, or 19.99 are almost never stored exactly.

float is the same problem, only worse

float is the 32-bit version — 23 fraction bits instead of 52. If double makes you nervous, float should make you run:

float f = 0.1f;
System.out.println(new BigDecimal(f));
0.100000001490116119384765625

The error is visible after only seven digits. Rule of thumb: never use float for anything you care about being correct, and never, ever for money.

When double is actually the right choice

Here is the part most "always use BigDecimal" advice gets wrong: most numbers in your program are not money, and for them double is the correct, fast, idiomatic choice.

Use double when the quantity is inherently approximate and a rounding error in the 16th digit is meaningless:

  • Physics, geometry, graphics, distances, coordinates.
  • Machine-learning features, statistics, averages, scientific measurements.
  • Sensor readings, percentages, ratios, anything already noisy.

For these, double is faster, uses less memory, and is supported directly by the CPU. Reaching for BigDecimal here is not "safer" — it is slower code solving a problem you do not have. The rule is about intent: double is for measuring, not for counting exact units.

When you do compare doubles, compare with a tolerance rather than ==:

double a = 0.1 + 0.2;
double epsilon = 1e-9;
System.out.println(Math.abs(a - 0.3) < epsilon);  // true

BigDecimal: the correct tool for money — and its three traps

When you are counting exact decimal units — currency, tax, interest, anything that must reconcile to the penny — you need a type that thinks in base 10. That is BigDecimal. It stores an arbitrary-precision integer (the unscaled value) plus a scale (how many digits sit after the decimal point), so 19.99 is stored exactly as 1999 × 10⁻².

But BigDecimal has three traps that catch nearly every newcomer. Learn them once and you will never debug them at 2 a.m.

Trap 1: never construct from a double

This is the mistake that quietly defeats the entire point of using BigDecimal:

System.out.println(new BigDecimal(0.1));     // from double
System.out.println(new BigDecimal("0.1"));   // from String
0.1000000000000000055511151231257827021181583404541015625
0.1

new BigDecimal(0.1) faithfully copies the already-broken double. You laundered a floating-point error into a "precise" type. Always build BigDecimal from a String (or from BigDecimal.valueOf(double), which routes through the string form). Once values are clean, arithmetic is exact:

BigDecimal a = new BigDecimal("0.10");
BigDecimal b = new BigDecimal("0.20");
System.out.println(a.add(b));
0.30

Trap 2: equals() compares scale, compareTo() compares value

Two BigDecimals that represent the same amount are not equal if their scales differ:

BigDecimal x = new BigDecimal("1.0");
BigDecimal y = new BigDecimal("1.00");
System.out.println(x.equals(y));          // false — scale 1 vs scale 2
System.out.println(x.compareTo(y) == 0);  // true  — same numeric value
false
true

For "is this the same amount?", always use compareTo() == 0. This trap also means you should never use BigDecimal as a HashMap key or in a HashSet unless you have normalized the scale first, because equals() and hashCode() both factor in scale.

Trap 3: division can throw — give it a scale and a rounding mode

A division with no exact decimal result throws at runtime:

new BigDecimal("1").divide(new BigDecimal("3"));
java.lang.ArithmeticException: Non-terminating decimal expansion;
no exact representable decimal result.

BigDecimal refuses to silently guess. You must tell it how precise and how to round:

import java.math.RoundingMode;

BigDecimal r = new BigDecimal("1")
        .divide(new BigDecimal("3"), 10, RoundingMode.HALF_EVEN);
System.out.println(r);
0.3333333333

That RoundingMode argument is not boilerplate — it is a decision, and the next section explains why.

Rounding modes: the default will surprise you

Ask a room of developers what 2.5 rounds to and everyone says 3. Ask what BigDecimal does by default and the room goes quiet — because the answer depends on the mode:

System.out.println(new BigDecimal("2.5").setScale(0, RoundingMode.HALF_UP));    // 3
System.out.println(new BigDecimal("2.5").setScale(0, RoundingMode.HALF_EVEN));  // 2
System.out.println(new BigDecimal("3.5").setScale(0, RoundingMode.HALF_EVEN));  // 4
3
2
4

HALF_UP is "round half away from zero" — the schoolbook rule. HALF_EVENbanker’s rounding — rounds a halfway value to the nearest even digit, so 2.5 → 2 but 3.5 → 4. Why? Over many roundings, always rounding .5 up introduces a consistent upward bias; banker’s rounding cancels it out, which is exactly what financial and statistical systems want. HALF_EVEN is the default for IEEE 754 floating-point and for MathContext.DECIMAL*.

Pick your rounding mode deliberately. "Whichever was the default" is how a billing system slowly drifts a few cents in the house’s favor and earns a regulator’s attention.

scale, precision, and MathContext (reference grade)

Two concepts govern every BigDecimal:

  • scale — the number of digits to the right of the decimal point. 19.99 has scale 2. Money math is usually fixed-scale: set scale 2 (or 4 for sub-cent interest) and a rounding mode, and keep it consistent everywhere.
  • precision — the total number of significant digits. Controlled with a MathContext, which bundles a precision with a rounding mode and is what you want for scientific-style arbitrary-precision work.
import java.math.MathContext;

MathContext mc = new MathContext(5, RoundingMode.HALF_EVEN);
System.out.println(new BigDecimal("2").divide(new BigDecimal("3"), mc));
0.66667

Rule of thumb: for money, think in scale (fixed decimal places); for science, think in precision (significant figures via MathContext).

Fixed-point: store cents as a long

There is a third option that is faster than BigDecimal and, for plain currency, often simpler: don’t store dollars at all — store the smallest unit (cents, or "minor units") as a long integer. Integers in base 2 are exact, so the original ten-dimes bug simply cannot happen:

long priceCents = 10;       // $0.10
long totalCents = 0;
for (int i = 0; i < 10; i++) {
    totalCents += priceCents;
}
System.out.println(totalCents + " cents = $" + (totalCents / 100.0));
System.out.println("Exact? " + (totalCents == 100));
100 cents = $1.0
Exact? true

This is the "minor units" model used by Stripe’s API, by many ledger systems, and by anyone who needs raw speed with exact whole-unit math. The trade-offs: you manage the decimal point yourself, you must watch for long overflow on very large sums (a long caps around 9.2 quintillion — still €92 quadrillion in cents), and anything requiring division with fractional results (interest, currency conversion, tax splits) pushes you back toward BigDecimal. For addition, subtraction, and counting, fixed-point long is hard to beat.

What precision actually costs

Correctness has a price, and it helps to know its size. The loop below adds the same value 20 million times using each approach (warmed up, single JVM run — a rough microbenchmark, not a rigorous JMH study):

long  (cents) :     ~0 ms   (JIT folds the integer loop to nearly free)
double        :     ~35 ms
BigDecimal    :    ~153 ms

So on this add-heavy workload, BigDecimal ran roughly 4–5× slower than double, and primitive long/double arithmetic is effectively free because the CPU and JIT handle it natively. BigDecimal allocates a new immutable object per operation, which adds CPU and garbage-collection pressure.

Read that the right way. For a request that does a handful of money calculations, 4–5× slower than "instant" is still instant — correctness wins, use BigDecimal without a second thought. The cost only matters in tight inner loops doing millions of operations, and that is precisely where fixed-point long (if the math allows) or double (if approximation is acceptable) earns its place. Never microbenchmark in production code; reach for JMH when numbers actually drive a decision.

The decision, in one table

Every section so far collapses into a single question — what am I really storing? — and one table:

Your data Use Why
Money, tax, billing, anything that must reconcile exactly BigDecimal (built from String, fixed scale, explicit RoundingMode) Exact base-10 arithmetic; correctness over speed
Currency with only +, −, ×, and you want max speed long of minor units (cents) Integers are exact and fastest; you own the decimal point
Measurements, science, graphics, ML, statistics double Inherently approximate; native CPU speed; compare with epsilon
Anything at all not float 7 digits of precision is a trap waiting to spring

If you remember only one sentence: count exact things with BigDecimal or long, measure approximate things with double, and never store money in float or double.

Going further (advanced references)

Once the fundamentals are solid, these are the tools and topics worth knowing:

  • Money & Currency API (JSR 354 / Moneta). A real MonetaryAmount type with currency units, formatting, and exchange-rate operations, so you stop passing raw BigDecimals and accidentally adding dollars to euros. The reference implementation is Moneta.
  • decimal4j. A library offering fixed-point decimal arithmetic backed by long, giving BigDecimal-style fixed-scale correctness at speeds close to primitives — the best of both worlds for high-throughput financial code.
  • Model money as a type, not a primitive. A small record Money(long minorUnits, Currency currency) (Java 16+) centralizes rounding and currency rules in one place and makes illegal states unrepresentable, instead of scattering BigDecimal math across the codebase.
  • Persistence. Map money columns to SQL DECIMAL/NUMERIC, never FLOAT/DOUBLE. In JPA/Hibernate, persist BigDecimal with an explicit precision and scale on the column so the database enforces them too.
  • JSON. Jackson can silently widen numbers to double during deserialization. Enable DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS (or bind to BigDecimal fields) so a price never round-trips through a double on its way in.
  • The IEEE 754 standard itself, if you want the ground truth on subnormals, infinities, NaN, and rounding — the foundation under every double you will ever write.

The thread connecting all of it is the very first line of this post. 0.1 + 0.2 is not equal to 0.3 because a double is a base-2 approximation. Once that clicks, every other decision — BigDecimal for exactness, long for speed, double for measurement, String constructors, banker’s rounding, fixed scales — stops being a rule to memorize and becomes a consequence you can derive. That is the difference between copying advice and understanding it.

FAQ

Why not use double for money?

A double is a base-2 (binary) approximation, so common base-10 amounts like 0.10 or 19.99 are not stored exactly. The tiny errors accumulate — ten dimes added as double come to 0.9999999999999999, not 1.0 — and money has to reconcile to the exact penny. Use BigDecimal or long cents instead, and never float.

Is BigDecimal always slower?

It is slower than primitives — roughly 4–5× slower than double in an add-heavy loop — because each operation allocates a new immutable object and adds garbage-collection pressure. But “slower” is relative: a request doing a handful of money calculations will not notice it, and correctness wins easily. The cost only matters in tight inner loops running millions of operations.

Should I use BigDecimal or long cents?

Use long minor units (cents) when your math is mostly addition, subtraction, and multiplication and you want maximum speed with exact whole-unit results — the model Stripe and many ledgers use. Switch to BigDecimal when you need division with fractional results (interest, currency conversion, tax splits), variable scale, or built-in rounding control. Both are correct; long is faster, BigDecimal is more flexible.

What is the difference between precision and scale?

Precision is the total number of significant digits in the number; scale is how many of those digits sit to the right of the decimal point. 19.99 has precision 4 and scale 2. For money you fix the scale (e.g. 2) and a rounding mode; for scientific work you control precision through a MathContext.

Why does BigDecimal.equals() return false?

Because equals() compares both value and scale, so new BigDecimal("1.0") and new BigDecimal("1.00") are not equal even though they represent the same amount. To compare numeric value only, use compareTo() == 0. This is also why a BigDecimal makes a risky HashMap key unless you normalize the scale first.


Sources & credits: IEEE 754 double-precision diagram by Codekaizen via Wikimedia Commons (CC BY-SA 4.0). All code samples were compiled and executed on OpenJDK; the output shown is the real program output.

Structured Concurrency in Java: From Your First StructuredTaskScope to Production Patterns (JDK 25/26)

Imagine a single web request that has to call three downstream services — a user service, an orders service, and a recommendations service — and combine their answers. Run them one after another and your user waits for the sum of all three. Run them at the same time and the request is only as slow as the slowest call. The catch is that “run them at the same time” has, for twenty years, been where Java code quietly turned fragile: threads leak, a failure in one call doesn’t stop the others, and a thread dump tells you nothing about which task started which.

Structured concurrency is the feature that fixes this. It treats a group of related tasks running in different threads as a single unit of work — they start together, they finish together, and if one fails the rest are cancelled automatically. This post starts from zero (if you’ve only ever written single-threaded Java, you’re fine) and ends at reference-grade detail: custom joiners, timeouts, observability, and the JDK 25/26 API exactly as it stands today.

Status (June 2026): Structured concurrency is a preview API. It previewed most recently in JDK 25 (JEP 505) and JDK 26 (JEP 525), and finalization is widely expected around JDK 27. Every example below targets JDK 26 and must be compiled and run with preview features enabled (shown later). The shape of the API is stable; only small details still move between previews.

The problem: “unstructured” concurrency

Here is the classic way to fan out two calls with an ExecutorService, the API we’ve had since Java 5:

Response handle() throws ExecutionException, InterruptedException {
    Future<String>  user  = executor.submit(() -> findUser());
    Future<Integer> order = executor.submit(() -> fetchOrder());

    String theUser  = user.get();   // join findUser
    int    theOrder = order.get();  // join fetchOrder
    return new Response(theUser, theOrder);
}

Continue reading Structured Concurrency in Java: From Your First StructuredTaskScope to Production Patterns (JDK 25/26)

Spring Security 5 to 6 to 7 Migration: SecurityFilterChain, Lambda DSL, and the Silent Authorization Changes

Of every breaking change in the Spring Boot 2 → 3 era, the removal of WebSecurityConfigurerAdapter generated the most confused stack traces I’ve debugged — because security configuration is the one place where “it compiles and runs” tells you almost nothing about whether it still protects anything. This guide migrates Spring Security 5 configurations to the 6.x component model (Spring Boot 3.x), covers what tightens further in Spring Security 7 (Spring Boot 4), and flags the places where a mechanical conversion quietly changes your authorization behaviour.

Continue reading Spring Security 5 to 6 to 7 Migration: SecurityFilterChain, Lambda DSL, and the Silent Authorization Changes