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

Surviving the AI Vulnerability Wave: How to Automate Spring Boot Dependency Patches

Something changed in the Spring ecosystem over the last year. AI-powered vulnerability scanners now monitor the CVE feeds, the Spring Security advisories, and the transitive dependency graphs of public repositories continuously — and they file patch requests the moment a fixable version lands. If you maintain more than a handful of Spring Boot services, you have probably noticed your pull-request queue filling with dependency bumps faster than any human can review them. Manual upgrades, one POM at a time, are no longer a sustainable remediation strategy. This guide shows how to build an automated dependency patching pipeline for Spring Boot: bot-driven update PRs, automated regression testing that decides whether a bump is safe, and canary deployments that catch the failures your tests miss.

Why Manual Spring Boot Patching No Longer Scales

A single Spring Boot application pulls in 150–300 transitive dependencies through its starters. Each of those is an independent source of CVEs. When a vulnerability lands in something deep in the graph — a Netty buffer, a SnakeYAML parser, a Tomcat connector — the fix usually arrives as a patch release that you inherit by bumping a single managed version. The problem is volume: across a fleet of services, the number of safe, boring, necessary upgrades per week now exceeds what a team can hand-review without either rubber-stamping (dangerous) or falling behind (also dangerous).

  • The scanners do not sleep. Automated tooling files patch PRs within hours of a CVE disclosure, not days.
  • Most bumps are trivial — but not all. The 95% that are pure patch releases are safe to automate; the 5% that quietly change behaviour are what an automated test gate exists to catch.
  • Falling behind compounds. Skip patches for two quarters and your “simple” upgrade becomes a multi-version migration with breaking changes.

The answer is not to review faster. It is to let bots open the PRs, let your test suite decide which ones are safe, and reserve human attention for the minority that actually need it.

Continue reading Surviving the AI Vulnerability Wave: How to Automate Spring Boot Dependency Patches

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.

Spring Framework 6 to 7 Migration Guide: Breaking Changes, Deprecated APIs, and Upgrade Checklist

I have spent the last few months taking a mid-sized Spring service from Spring Framework 6.2 to Spring Framework 7.0, and the thing nobody tells you up front is this: most of the migration content you find is actually about Spring Boot. Boot 4 gets all the headlines. But Boot 4 sits on top of Framework 7, and the changes that broke my build, my tests, and (twice) my production behaviour came from the core framework — bean lifecycle, AOT metadata, the Jakarta cutover, and a pile of quietly removed APIs.

This guide is about the layer underneath Boot. If you maintain a library, a non-Boot Spring application, or you just want to understand why Boot 4 forces certain changes, this is the migration you need to read. Framework 7.0 went GA on 13 November 2025, and everything below is verified against the official 7.0 release notes — not guessed from a beta.

Continue reading Spring Framework 6 to 7 Migration Guide: Breaking Changes, Deprecated APIs, and Upgrade Checklist

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)

Interpreter Design Pattern in Java: Building an Expression Evaluator

You need to evaluate expressions like (5 + 3) * 2 at runtime — from a config file, a business rule engine, or a user-defined formula. One approach: write a hand-rolled parser. Another: use the Interpreter pattern — map each grammar rule to a class, compose them into an abstract syntax tree (AST), then evaluate by calling interpret() on the root.

Interpreter is a GoF behavioral pattern. You encounter it constantly: SQL engines, Java’s regex Pattern class, Spring Expression Language (SpEL), Thymeleaf templates, and JEXL all apply this pattern. Understanding its structure explains how expression languages work under the hood.

Continue reading Interpreter Design Pattern in Java: Building an Expression Evaluator

GoF Design Patterns in Java: The Complete Guide (All 23 Patterns)

The Gang of Four book — Design Patterns: Elements of Reusable Object-Oriented Software (Gamma, Helm, Johnson, Vlissides, 1994) — catalogued 23 patterns across three categories. Thirty years later, these patterns remain the shared vocabulary of object-oriented design. Every pattern in this series has a dedicated article with full runnable Java 17 code, a UML diagram, console output, and real JDK usage examples.

💡 How to use this guide: Each pattern link below goes to a standalone article. If you’re new to patterns, start with Singleton (simplest), then Strategy (most practical), then Observer (most commonly needed). If you’re preparing for a design interview, work through all three categories in order. Source code for every pattern is at ankurm.com/git.app/asmhatre/design-patterns.

Continue reading GoF Design Patterns in Java: The Complete Guide (All 23 Patterns)