Modern processors can perform the same operation on several data elements at once through SIMD, or single instruction, multiple data. Java 25 exposes an incubating API for expressing these computations explicitly through the jdk.incubator.vector module.

The Vector API is still incubating in Java 25. It is not a preview feature and it is not yet a permanent Java SE API. Java 25 contains the tenth incubation of the API, so applications must explicitly add the incubator module when they compile and run.

Java 25 Vector API and SIMD

A scalar loop normally performs an operation on one pair of values at a time. A vector operation describes the same operation across multiple lanes. On supported hardware, HotSpot can compile those vector operations to efficient SIMD instructions.

The exact number of lanes depends on the element type, vector species and target CPU. Java therefore provides preferred vector species instead of requiring an application to assume that every processor has the same SIMD width.

For example, this code loads eight integers into a 256-bit vector and adds the vector to itself:

import jdk.incubator.vector.IntVector;
import jdk.incubator.vector.VectorSpecies;

static final VectorSpecies<Integer> SPECIES = IntVector.SPECIES_256;

void main() {
    int[] numbers = {10, 20, 12, 28, 10, 19, 101, 799};

    var vector = IntVector.fromArray(SPECIES, numbers, 0);

    IO.println(vector);
    IO.println(vector.add(vector));
}

With Java 25 compact source files and instance main methods, a small demonstration like this does not require a wrapper class or a traditional public static void main(String[] args) declaration. The IO.println method also keeps console output concise.

The output is:

[10, 20, 12, 28, 10, 19, 101, 799]
[20, 40, 24, 56, 20, 38, 202, 1598]

This is the central idea behind the Vector API: express a lane-wise computation in Java and let the JIT compiler map it to appropriate vector instructions when the platform supports them.

The prefix sum problem

A prefix sum, also called a scan, replaces each element with the sum of that element and every element that precedes it.

Given these values:

10, 20, 12, 28, 10, 19, 101, 799

the inclusive prefix sums are:

10, 30, 42, 70, 80, 99, 200, 999

The straightforward scalar implementation is simple and usually the right baseline:

void prefixSum(int[] numbers) {
    for (int i = 1; i < numbers.length; i++) {
        numbers[i] += numbers[i - 1];
    }
}

This algorithm performs a linear pass over the array. Each result depends on the previous result, which makes a prefix sum more challenging to vectorize than an operation in which every lane is completely independent.

Parallel prefix calculation inside a vector

For an eight-lane vector, an inclusive prefix sum can be expressed as a sequence of shift-and-add stages. The shift distance doubles at every stage:

Start:
  [10, 20, 12, 28, 10, 19, 101, 799]

Shift by 1 and add:
  [10, 30, 32, 40, 38, 29, 120, 900]

Shift by 2 and add:
  [10, 30, 42, 70, 70, 69, 158, 929]

Shift by 4 and add:
  [10, 30, 42, 70, 80, 99, 200, 999]

There are three dependency stages for eight lanes because the shift distances are 1, 2 and 4. More generally, this style of parallel scan uses a logarithmic number of stages within the vector.

That does not mean an eight-element prefix sum literally takes three CPU operations or three clock cycles. Each Java Vector API expression can compile into one or more machine instructions, and actual performance depends on the CPU, JIT compilation, vector width and surrounding code.

Java 25 prefix sum with IntVector

The following Java 25 example performs the three shift-and-add stages with an eight-lane IntVector:

import jdk.incubator.vector.IntVector;
import jdk.incubator.vector.VectorSpecies;

static final VectorSpecies<Integer> SPECIES = IntVector.SPECIES_256;

void main() {
    int[] numbers = {10, 20, 12, 28, 10, 19, 101, 799};

    if (SPECIES.length() != numbers.length) {
        throw new IllegalStateException(
            "This example requires an eight-lane IntVector."
        );
    }

    var vector = IntVector.fromArray(SPECIES, numbers, 0);

    IO.println("Original:   " + vector);

    vector = vector.add(vector.unslice(1));
    vector = vector.add(vector.unslice(2));
    vector = vector.add(vector.unslice(4));

    IO.println("Prefix sum: " + vector);
}

The result is:

Original:   [10, 20, 12, 28, 10, 19, 101, 799]
Prefix sum: [10, 30, 42, 70, 80, 99, 200, 999]

The call to unslice(1) moves the existing lanes to the right and fills the vacated lane with zero. The subsequent calls shift by two and four lanes. Adding each shifted vector to the current vector progressively propagates the partial sums.

Compile and run the Java 25 Vector API example

Because jdk.incubator.vector is an incubator module, add it explicitly when compiling and running the program:

javac --add-modules jdk.incubator.vector PrefixSum.java
java --add-modules jdk.incubator.vector PrefixSum

The JDK will also report that an incubating module is in use. That warning is expected.

SPECIES_256 vs. SPECIES_PREFERRED

The earlier version of this example used IntVector.SPECIES_PREFERRED. That is useful in production vector code because it lets the runtime select a vector shape appropriate for the current platform.

For this particular tutorial, however, the algorithm and sample data intentionally assume exactly eight integer lanes. An integer occupies 32 bits, so IntVector.SPECIES_256 gives the example eight lanes and makes the three shifts of 1, 2 and 4 easy to see.

Production code should normally be written so it does not assume that the preferred species contains exactly eight lanes. Large arrays also require processing multiple vector blocks, handling the carry from one block to the next and dealing with any tail elements that do not fill an entire vector.

Java 25 Vector API status

The Vector API first appeared as an incubating API in JDK 16. Java 25 contains its tenth incubation under JEP 508.

The API remains in the jdk.incubator.vector module while OpenJDK continues to evolve its implementation and coordinate its future design with Project Valhalla. Java 25 also includes Vector API implementation improvements, including expanded Float16 auto-vectorization on supporting x64 processors and integration with native mathematical-function libraries through the Foreign Function and Memory API.

The API supports vectors containing byte, short, integer, long, float and double values, with standard vector shapes including 64, 128, 256 and 512 bits as well as a platform-dependent maximum shape.

SIMD performance requires measurement

The Vector API gives Java developers a much more predictable way to express SIMD-friendly computations, but vector code should still be benchmarked rather than assumed to be faster.

For a tiny eight-element array, JVM startup, JIT compilation and other overhead can dwarf the cost of the calculation itself. Vectorization becomes most interesting when the same operations are repeatedly applied to substantial amounts of data.

For serious performance comparisons, use JMH and test on the same CPU architecture and JDK configuration used in production. Compare the vector implementation against a clear scalar baseline and verify that the optimized algorithm still produces identical results.

Java 25 SIMD and Vector API summary

Java 25's Vector API makes explicit SIMD programming available directly from Java while remaining portable across supported processor architectures. The prefix sum example is especially useful because it demonstrates that vector programming involves more than simply replacing a loop with a vector addition.

The eight-lane example performs a parallel scan through shift-and-add stages of one, two and four lanes. It produces the expected prefix sums of [10, 30, 42, 70, 80, 99, 200, 999] and illustrates how algorithms can be reorganized to take advantage of vector operations.


Cameron McKenzie

Cameron McKenzie is an AWS Certified AI Practitioner, Machine Learning Engineer, Solutions Architect and author of many popular books in the software development and Cloud Computing space. His growing YouTube channel training devs in Java, Spring, AI and ML has well over 30,000 subscribers.