Prev Next

Java / Java 25 Vector API Interview questions

Last updated

1. What is the Java Vector API? 2. What is SIMD and why does it matter for Java developers? 3. What problem does the Vector API solve? 4. What is the status of the Vector API in Java 25? 5. How do you compile and run code that uses the Vector API in Java 25? 6. What is JEP 508? 7. What is a VectorSpecies? 8. What are the main classes of the Vector API? 9. What is SPECIES_PREFERRED? 10. How do you load an array into a vector and store it back? 11. How do you add two float arrays with the Vector API? 12. What is a VectorMask? 13. What is the purpose of loopBound? 14. How do you handle leftover elements after the vector loop? 15. What are VectorOperators? 16. How do you sum all elements of an array using reduceLanes? 17. What is VectorShape? 18. How do you configure Maven or Gradle for the Vector API? 19. What lane-wise operations does FloatVector support? 20. What are the common use cases of the Vector API? 21. What is a VectorShuffle? 22. What is the difference between lane-wise and cross-lane operations? 23. How does the Vector API differ from auto-vectorization? 24. Why is the Vector API still incubating in Java 25? 25. How does Vector API code get compiled into SIMD instructions? 26. What happens when the CPU does not support the chosen species? 27. Why should a VectorSpecies be declared static final? 28. Why is Vector API code slow at startup before warm-up? 29. How do you compute a dot product with fma? 30. How do you write branch-free conditional logic with masks? 31. How does fromMemorySegment work with off-heap data? 32. How do you process unsigned bytes such as pixels safely? 33. What is the difference between convert and reinterpret in the Vector API? 34. How do gather and scatter work in the Vector API? 35. Why can floating-point reductions in the Vector API differ from a scalar loop? 36. What happens when integer lanes overflow in the Vector API? 37. When should you use a masked tail instead of a scalar tail? 38. When should you choose SPECIES_256 instead of SPECIES_PREFERRED? 39. When should you not use the Vector API? 40. How do you benchmark Vector API code correctly? 41. How do you troubleshoot slow Vector API code? 42. How does the Vector API relate to Project Valhalla? 43. Why shouldn't you use == or synchronized on Vector instances? 44. What is the difference between the Vector API and JNI or native SIMD libraries? 45. How does the Vector API stay portable across CPU architectures? 46. How do compress and expand work on a vector? 47. How do slice and unslice work? 48. How does the Vector API differ from parallel streams? 49. How do you find the first matching element using SIMD? 50. How can you optimize memory-bound vector loops?

1. What is the Java Vector API?

The Vector API lives in the jdk.incubator.vector module and lets you write explicit SIMD (single instruction, multiple data) code in plain Java. You load a chunk of an array into a vector and apply one operation to every lane at once, instead of looping element by element.

At run time the C2 JIT compiler turns those calls into the widest vector instructions the CPU offers (SSE, AVX2, AVX-512, NEON, SVE). No native code or JNI is involved.

In Java 25 the API is still incubating, delivered by JEP 508, so it can change between releases.

Take quiz
Where does the Vector API live in Java 25?
In java.base as a final API
In java.desktop next to AWT
In the jdk.incubator.vector module
In java.sql with the JDBC drivers
What turns Vector API calls into CPU SIMD instructions?
The javac compiler at build time
A native library loaded through JNI
The garbage collector
The C2 JIT compiler at run time

2. What is SIMD and why does it matter for Java developers?

SIMD means one CPU instruction works on several data elements held in a single wide register. A 256-bit register holds eight 32-bit ints, so one add instruction performs eight additions.

It matters whenever you run the same arithmetic over big arrays: numeric code, image filters, checksums, similarity search, text scanning. Scalar loops leave most of the register width unused.

Scalar add SIMD add (256-bit ints)
1 element per instruction 8 elements per instruction
Uses a general register Uses a vector register (YMM/NEON/SVE)
Loop runs n times Loop runs roughly n / 8 times

Take quiz
How many 32-bit int lanes fit in a 256-bit vector?
Eight
Four
Sixteen
Thirty-two
Which description fits SIMD best?
Several threads running the same method
Several JVMs sharing one heap
One instruction applied to several data elements at once

3. What problem does the Vector API solve?

It solves the unpredictability of auto-vectorization. C2 can vectorize simple loops by itself, but a small change such as an extra method call, a conditional, or an unusual reduction can silently switch it off. You get no error, just a slower loop.

With the Vector API you state the vector operations yourself. If the platform supports them, you get SIMD code; the result no longer depends on the optimizer recognizing your loop shape.

Take quiz
Why add a Vector API when C2 already auto-vectorizes?
C2 cannot vectorize any loop
The API replaces the JIT compiler
Auto-vectorization is implicit and fragile, while the API makes vectorization explicit
Auto-vectorization only works on the GPU
What typically happens when a small code change defeats auto-vectorization?
The JVM throws a VectorizationException
The build fails
The JVM switches to parallel streams
The loop silently runs as scalar code

4. What is the status of the Vector API in Java 25?

In Java 25 the Vector API is in its tenth incubation, delivered by JEP 508. Java 25 is an LTS release, but this particular API is not final: it stays in the jdk.incubator.vector module and can still change.

It is not a preview feature, so --enable-preview is not used. You add the module explicitly with --add-modules jdk.incubator.vector.

OpenJDK has said it will keep incubating until the Project Valhalla features it needs arrive as previews.

Take quiz
In which round of incubation does the Vector API appear in Java 25?
The tenth
The first
The third
It is final, so none
Does using the Vector API in Java 25 need --enable-preview?
No, it is an incubator module enabled with --add-modules
Yes, it is a preview API
No, it is final and needs no flags

5. How do you compile and run code that uses the Vector API in Java 25?

Because the API is an incubator module, it is not resolved by default. Pass --add-modules jdk.incubator.vector to both the compiler and the JVM.

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

For a named module, declare requires jdk.incubator.vector; in module-info.java. At startup the JVM prints a warning that an incubating module is in use. That is expected and harmless.

Take quiz
Which command compiles a class that uses the Vector API?
javac --enable-preview --release 25 VecDemo.java
javac -cp jdk.incubator.vector VecDemo.java
javac --add-modules jdk.incubator.vector VecDemo.java
javac VecDemo.java with no extra flags
What does the JVM do when an incubator module is resolved at startup?
It refuses to start
It silently disables JIT compilation
It prompts for confirmation
It prints a warning that an incubating module is in use

6. What is JEP 508?

JEP 508 is the tenth incubation round of the Vector API, integrated into JDK 25. The Vector API began in JDK 16 with JEP 338 and has been re-incubated every release since. JEP 508 follows JEP 489 from JDK 24.

It has one API change: VectorShuffle can now be read from and written to a MemorySegment. It also carries two implementation-level changes.

The goal stays the same: let developers express vector computations that reliably compile to optimal vector instructions on supported CPUs.

Take quiz
What API change came with JEP 508?
VectorShuffle can be read from and written to a MemorySegment
Addition of a GPU backend
Removal of VectorMask
Vectors became final Java SE classes
Which earlier JEP is the ninth incubator that JEP 508 follows?
JEP 338 in JDK 16
JEP 489 in JDK 24
JEP 506 in JDK 25
JEP 444 in JDK 21

7. What is a VectorSpecies?

A VectorSpecies describes a vector type: the element type plus the total vector size (its shape). Together they decide how many lanes a vector has. For example FloatVector.SPECIES_256 means float elements in 256 bits, which is 8 lanes.

Almost every factory method needs one, such as fromArray(species, array, offset) or zero(species). You can also query it:

  • length() for lane count
  • elementSize() in bits
  • vectorBitSize() for total size
  • loopBound(n) and indexInRange(i, n) for loop control
Take quiz
What does FloatVector.SPECIES_256 describe?
A 256-element float array
A float vector with 256 threads
Float elements packed into a 256-bit vector of 8 lanes
A 256-byte memory segment
Which VectorSpecies method returns the number of lanes?
elementSize()
loopBound()
vectorShape()
length()

8. What are the main classes of the Vector API?

The API is small. These are the types you will meet in almost every example:

Type Role
Vector<E> Abstract base class for all vectors
IntVector, FloatVector, DoubleVector, LongVector, ShortVector, ByteVector Typed vectors with the actual operations
VectorSpecies<E> Element type plus size; the factory key
VectorShape Bit size: 64, 128, 256, 512 or max
VectorMask<E> One boolean per lane
VectorShuffle<E> Lane index mapping for rearranging
VectorOperators Constants such as ADD, MUL, GT, I2F

Take quiz
Which class holds one boolean per lane?
VectorMask
VectorShape
VectorShuffle
VectorSpecies
Which class supplies constants such as ADD and GT?
VectorSpecies
VectorOperators
VectorShape
Vector

9. What is SPECIES_PREFERRED?

SPECIES_PREFERRED is a constant on each typed vector class, for example FloatVector.SPECIES_PREFERRED. It gives the species with the best shape for the current CPU, chosen by the JVM at startup. On x64 that is often 256 or 512 bits, depending on what the hardware and JVM settings allow; on AArch64 it depends on NEON or SVE.

Use it by default so one build adapts to the machine. Always read the lane count from SP.length() instead of hard-coding 8 or 16.

var SP = FloatVector.SPECIES_PREFERRED;
int lanes = SP.length();   // varies per machine

Take quiz
What does SPECIES_PREFERRED give you?
Always a 512-bit species
The smallest shape the JVM supports
The species with the best vector size for the current CPU
A species chosen at compile time by javac
How should loop code get the lane count?
By hard-coding 8
From Runtime.availableProcessors()
From the array length
From species.length()

10. How do you load an array into a vector and store it back?

Use the static fromArray factory to load and intoArray to store. Both take an offset into the array, and the API bounds-checks them.

var SP = IntVector.SPECIES_PREFERRED;
int[] src = new int[1024];
int[] dst = new int[1024];

IntVector v = IntVector.fromArray(SP, src, 0);   // reads SP.length() ints
v.add(1).intoArray(dst, 0);                       // writes them back

A masked overload exists for both, which is how you deal with the end of an array. Unaligned offsets are fine; the API has no alignment requirement.

Take quiz
Which method writes a vector back to a Java array?
intoArray
toArray on the species
storeArray
writeTo
Does fromArray require the offset to be aligned to the vector size?
No, any valid offset works
Yes, it must be a multiple of 64
Yes, it must be a multiple of SP.length() bytes

11. How do you add two float arrays with the Vector API?

Run a vector loop up to loopBound, then finish the leftover elements with plain scalar code.

static final VectorSpecies<Float> SP = FloatVector.SPECIES_PREFERRED;

static void add(float[] a, float[] b, float[] c) {
    int i = 0;
    int upper = SP.loopBound(a.length);
    for (; i < upper; i += SP.length()) {
        FloatVector va = FloatVector.fromArray(SP, a, i);
        FloatVector vb = FloatVector.fromArray(SP, b, i);
        va.add(vb).intoArray(c, i);
    }
    for (; i < a.length; i++) {      // scalar tail
        c[i] = a[i] + b[i];
    }
}

Note the species is static final so the JIT can treat it as a constant.

Take quiz
What does the second loop in the example do?
Runs the vector loop a second time for safety
Validates the first loop's output
Handles the leftover elements that do not fill a whole vector
Releases native memory
Why is SP declared static final?
Because VectorSpecies is not thread-safe
Because javac requires it
To make the arrays immutable
So the JIT can treat it as a constant

12. What is a VectorMask?

A VectorMask holds one boolean per lane and decides which lanes an operation applies to. Masks come from comparisons, or from helpers such as species.indexInRange(i, n).

You can pass a mask to masked loads and stores, to blend, and to masked lane-wise ops. Useful queries are anyTrue(), allTrue(), trueCount() and firstTrue().

VectorMask<Float> positive = v.compare(VectorOperators.GT, 0f);
int count = positive.trueCount();

Take quiz
What do you get from v.compare(VectorOperators.GT, 0f)?
A VectorMask with one boolean per lane
A boolean for the whole vector
A new FloatVector of 0s and 1s
A VectorShuffle
Which mask method tells you the index of the first set lane?
anyTrue()
firstTrue()
trueCount()
allTrue()

13. What is the purpose of loopBound?

species.loopBound(n) returns the largest multiple of the lane count that is not greater than n. It is the safe upper limit for a loop that only processes full vectors.

With 8 lanes and 29 elements, loopBound(29) is 24. The vector loop covers indexes 0 to 23, and the remaining 5 elements are handled separately as the tail.

int upper = SP.loopBound(a.length);
for (int i = 0; i < upper; i += SP.length()) { /* full vectors only */ }

Take quiz
With 8 lanes, what does loopBound(29) return?
29
32
24
21
What is loopBound used for?
Choosing the species shape
Setting the JIT warm-up count
Limiting heap size
Setting the upper limit of a loop that processes only full vectors

14. How do you handle leftover elements after the vector loop?

You have two options.

  1. Scalar tail: after the vector loop, run a normal for loop over the remaining indexes. It is simple and works everywhere.
  2. Masked tail: process the last partial chunk with a mask from species.indexInRange(i, n), so lanes past the end are switched off.

Masked loads and stores never touch the disabled lanes, so no out-of-bounds exception occurs for them. Always test arrays whose length is not a multiple of the lane count, including length 0 and 1.

Take quiz
What happens to masked-off lanes in a masked fromArray?
They are not read, so they cannot cause an out-of-bounds error
They are read and zeroed
They throw IndexOutOfBoundsException
They are read as NaN
Which test input is most likely to expose a tail bug?
An array of exactly 1024 elements
An array whose length is not a multiple of the lane count
A very large array
An array of all zeros

15. What are VectorOperators?

VectorOperators is a class of constants that name operations, so you can pass them to generic methods such as lanewise, compare, reduceLanes and convert.

  • Arithmetic and bitwise: ADD, SUB, MUL, AND, OR, XOR
  • Comparisons: EQ, NE, LT, GT, LE, GE
  • Conversions: I2F, F2I, B2S and similar
v.lanewise(VectorOperators.MUL, w);
v.compare(VectorOperators.GE, 0);
v.reduceLanes(VectorOperators.MAX);

Take quiz
Which method takes a VectorOperators constant to pick the operation?
fromArray
loopBound
lanewise
length
What kind of constant is VectorOperators.I2F?
A comparison
A shuffle
A species
A conversion from int lanes to float lanes

16. How do you sum all elements of an array using reduceLanes?

Keep a running accumulator vector inside the loop and call reduceLanes only once at the end. Reducing on every iteration forces a slow cross-lane step each time.

static int sum(int[] a) {
    var SP = IntVector.SPECIES_PREFERRED;
    var acc = IntVector.zero(SP);
    int i = 0;
    for (int upper = SP.loopBound(a.length); i < upper; i += SP.length()) {
        acc = acc.add(IntVector.fromArray(SP, a, i));
    }
    int total = acc.reduceLanes(VectorOperators.ADD);
    for (; i < a.length; i++) total += a[i];
    return total;
}

Take quiz
When should reduceLanes be called in a sum loop?
Once, after the vector loop finishes
On every iteration
Before the loop starts
Only inside the scalar tail
Why is reduceLanes inside the hot loop a poor choice?
It is not supported for ints
It is a cross-lane operation and costs more than a lane-wise add
It allocates a new array each call
It changes the species

17. What is VectorShape?

VectorShape is an enum for the total bit size of a vector: S_64_BIT, S_128_BIT, S_256_BIT, S_512_BIT and S_Max_BIT. S_Max_BIT is the largest size the platform supports.

A species is shape plus element type, so you can build one with VectorSpecies.of(Float.class, VectorShape.S_256_BIT). VectorShape.preferredShape() returns the platform's preferred size.

Take quiz
What does VectorShape.S_Max_BIT mean?
A fixed 1024-bit shape
The size of the CPU cache line
The largest vector size the platform supports
The maximum array length
A VectorSpecies is built from which two things?
A mask and a shuffle
A thread count and a heap size
An operator and a lane
An element type and a VectorShape

18. How do you configure Maven or Gradle for the Vector API?

Add the incubator module to compile time and to every JVM that runs the code, including tests and benchmarks.

Maven (compiler plus surefire):

<plugin>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
    <source>25</source>
    <target>25</target>
    <compilerArgs><arg>--add-modules</arg><arg>jdk.incubator.vector</arg></compilerArgs>
  </configuration>
</plugin>
<plugin>
  <artifactId>maven-surefire-plugin</artifactId>
  <configuration><argLine>--add-modules jdk.incubator.vector</argLine></configuration>
</plugin>

Gradle:

tasks.withType(JavaCompile) { options.compilerArgs += ['--add-modules', 'jdk.incubator.vector'] }
tasks.withType(Test)        { jvmArgs '--add-modules', 'jdk.incubator.vector' }
tasks.withType(JavaExec)    { jvmArgs '--add-modules', 'jdk.incubator.vector' }

If the compiler cannot see the module, try source and target instead of --release.

Take quiz
Where must --add-modules jdk.incubator.vector be set in a Maven build?
In compilerArgs and in the surefire argLine
Only in pom.xml properties for the license
Only in the compiler plugin
Only in settings.xml
Why do tests need the flag too?
Tests are compiled by a different javac
Tests run in a separate JVM that must also resolve the module
JUnit requires incubator modules
Surefire disables the module path

19. What lane-wise operations does FloatVector support?

A FloatVector covers the usual arithmetic plus a set of math functions, all applied per lane.

Group Examples
Arithmetic add, sub, mul, div, neg, abs
Fused and roots fma(b, c), sqrt()
Min and max min, max
Math functions lanewise(VectorOperators.EXP / LOG / SIN / COS ...), pow
Comparison compare(op, other) returning a mask

Integer vectors add bitwise and shift operations such as AND, OR, XOR, LSHL, LSHR, ASHR.

Take quiz
Which FloatVector method computes a*b+c in one fused step?
add
blend
fma
slice
Which extra operation group do integer vectors have that floats do not?
Square root
Mask comparison
Fused multiply-add
Bitwise and shift operations

20. What are the common use cases of the Vector API?

It fits code that applies the same arithmetic to large, contiguous data:

  • Numeric work: dot products, SAXPY, matrix multiply, statistics
  • Similarity search: vector distance for embeddings; Apache Lucene uses it for this
  • Media: image filters, audio buffers, pixel format conversion
  • Bytes and text: scanning for a byte, UTF-8 validation, parsing
  • Crypto and hashing: bulk bit operations

It is a poor fit for pointer-heavy structures and branchy business logic.

Take quiz
Which workload suits the Vector API best?
Computing distances between large float arrays
Walking a linked list
Handling HTTP requests
Parsing small config files
Which library is known to use the Vector API for vector similarity?
Hibernate
Apache Lucene
Log4j
JUnit

21. What is a VectorShuffle?

A VectorShuffle is a list of lane indexes that describes how to move lanes around. You build one, then apply it with vector.rearrange(shuffle): result lane i takes the value from source lane shuffle[i].

var SP = IntVector.SPECIES_128;                       // 4 lanes
var rev = VectorShuffle.fromValues(SP, 3, 2, 1, 0);
var v   = IntVector.fromArray(SP, new int[]{10, 20, 30, 40}, 0);
v.rearrange(rev);   // [40, 30, 20, 10]

Shuffles are a cross-lane operation, so use them sparingly in hot loops. Java 25 adds reading and writing a shuffle from a MemorySegment.

Take quiz
What does rearrange(shuffle) do?
Sorts the lanes in ascending order
Masks off selected lanes
Builds a result whose lane i takes source lane shuffle[i]
Converts lanes to another type
What does the shuffle fromValues(SP, 3, 2, 1, 0) do to a 4-lane vector?
Rotates by one lane
Zeroes all lanes
Duplicates lane 0
Reverses the lane order

22. What is the difference between lane-wise and cross-lane operations?

A lane-wise operation treats each lane independently: lane i of the result depends only on lane i of the inputs. add, mul and compare are examples. They map directly to a single fast instruction.

A cross-lane operation moves or combines data between lanes, for example reduceLanes, rearrange, slice, compress and expand. They are usually slower and CPU support varies.

Lane-wise Cross-lane
add, mul, fma, compare, blend reduceLanes, rearrange, slice, compress
No data moves between lanes Data crosses lane boundaries
Cheap, easy to pipeline Costlier; keep out of inner loops when possible

Take quiz
Which of these is a cross-lane operation?
reduceLanes
add
mul
compare
Why keep cross-lane operations out of inner loops where possible?
They are not allowed in loops
They are generally slower than lane-wise instructions
They allocate on the Java heap
They need a different species

23. How does the Vector API differ from auto-vectorization?

Both end up as SIMD instructions, but the control is different. Auto-vectorization (C2's SuperWord) is a silent optimization on scalar loops. The Vector API is code where you spell out the vector steps.

Auto-vectorization Vector API
Implicit, done by C2 if the loop shape qualifies Explicit; you write the vector operations
No guarantee, small edits can disable it Predictable when the species is supported
Works on ordinary scalar code, no flags Needs --add-modules jdk.incubator.vector
Limited to patterns the optimizer knows Supports masks, shuffles, gathers, reductions

Try auto-vectorization first. Reach for the API when you have measured that it is not enough.

Take quiz
Which approach needs an extra module flag?
C2 auto-vectorization
Both of them
The Vector API
Neither
What is a sound first step before using the Vector API?
Rewrite everything in JNI
Disable the JIT
Switch to parallel streams
Check whether plain scalar code is already auto-vectorized and fast enough

24. Why is the Vector API still incubating in Java 25?

The API depends on features that Project Valhalla has not delivered yet. Today a Vector is an ordinary object, and the JIT has to work hard to remove the boxing around it. Value classes would let vectors behave like primitives with no object identity.

OpenJDK has stated that it will keep re-incubating the API until the needed Valhalla features arrive as previews. Then the API will be adapted and promoted from incubation to preview.

That is why JEP 508 is the tenth round, and why code written against it should expect small changes later.

Take quiz
Why does the Vector API remain incubating?
It waits on Project Valhalla features such as value classes
It has known security holes
Only x64 CPUs support it
It is waiting for GPU drivers
What is the planned next stage after incubation?
Immediate removal
Promotion to a preview API
Merging into java.lang
Becoming a JVM flag

25. How does Vector API code get compiled into SIMD instructions?

The classes in jdk.incubator.vector are written in Java, but the C2 compiler knows the hot methods as intrinsics. When C2 compiles your loop, it replaces calls like add or fromArray with matching machine instructions, and it tries to eliminate the vector objects so values stay in registers.

flowchart TD
  A["Your loop with FloatVector calls"] --> B{Hot enough for C2?}
  B -- No --> C["Interpreter or C1 runs the Java fallback code"]
  B -- Yes --> D["C2 intrinsifies vector operations"]
  D --> E["Escape analysis removes vector objects"]
  E --> F["CPU specific SIMD code: AVX2, AVX-512, NEON, SVE"]

If C2 cannot intrinsify an operation, or the vector objects escape, it falls back to slow object-based Java code.

Take quiz
Which compiler turns Vector API operations into SIMD instructions?
C1
The bytecode interpreter
C2
javac
What must C2 usually achieve to avoid slow object code?
Convert vectors to strings
Pin threads to CPUs
Disable inlining
Eliminate the allocation of vector objects

26. What happens when the CPU does not support the chosen species?

The code still runs, but without hardware acceleration. The JVM falls back to a plain Java implementation that treats lanes as array elements and allocates objects. It is correct but can be much slower than a simple scalar loop.

This is the main risk of hard-coding a species such as SPECIES_512: on a CPU without AVX-512 you may pay for it. SPECIES_PREFERRED avoids that by choosing a size the machine handles well.

Situation Result
Species supported by CPU and intrinsified Fast SIMD code
Species too wide for CPU Correct but slow fallback
Code still in interpreter or C1 Fallback code until C2 compiles it

Take quiz
What happens if the chosen species is too wide for the CPU?
The code runs correctly but through a slow fallback
It throws UnsupportedOperationException at load time
The JVM silently picks a smaller species
The program crashes with SIGILL
Which choice lowers the risk of an unsupported species?
Hard-coding SPECIES_512
Using SPECIES_PREFERRED
Hard-coding SPECIES_MAX only on ARM
Using ByteVector only

27. Why should a VectorSpecies be declared static final?

The JIT can only generate tight SIMD code if the species is a compile-time constant. A static final field lets C2 fold the lane count, element type and shape directly into the compiled loop.

If the species sits in a non-final field, a local that changes, or a method parameter that C2 cannot prove constant, the compiler cannot specialize. The code then uses slower generic paths and often allocates vector objects.

// good
static final VectorSpecies<Float> SP = FloatVector.SPECIES_PREFERRED;

// risky: not a constant to the JIT
VectorSpecies<Float> sp = FloatVector.SPECIES_PREFERRED;

Take quiz
Why does a static final species help performance?
It makes the arrays immutable
It reduces garbage collection pauses only
C2 can treat shape and lane count as constants and specialize the loop
It enables --enable-preview
What may happen when the species is a plain non-final field?
javac rejects the code
The JVM throws IllegalStateException
Lane count becomes zero
C2 may use slower generic code and allocate vector objects

28. Why is Vector API code slow at startup before warm-up?

The speed comes from C2, and C2 only compiles methods after they run enough times. Until then the interpreter and C1 execute the Java fallback implementation, which allocates vector objects for every operation.

So a one-shot call on a fresh JVM can look slower than a scalar loop. The fix is to let hot loops run long enough for C2 to compile them, and to benchmark with a proper harness such as JMH that includes warm-up iterations.

Very short-lived programs and cold paths may never benefit.

Take quiz
Why can the first calls of a Vector API loop be slow?
The interpreter and C1 run the slow Java fallback until C2 compiles the loop
The JVM loads the GPU driver on first use
The garbage collector disables SIMD
Species creation takes seconds
What belongs in a benchmark of Vector API code?
A single timed call
Warm-up iterations before measurement
Running with -Xint
Disabling C2

29. How do you compute a dot product with fma?

fma(b, c) computes this * b + c per lane with a single rounding, and on most CPUs in one instruction. A dot product keeps an accumulator vector and fuses the multiply and add.

static float dot(float[] a, float[] b) {
    var SP = FloatVector.SPECIES_PREFERRED;
    var acc = FloatVector.zero(SP);
    int i = 0;
    for (int upper = SP.loopBound(a.length); i < upper; i += SP.length()) {
        var va = FloatVector.fromArray(SP, a, i);
        var vb = FloatVector.fromArray(SP, b, i);
        acc = va.fma(vb, acc);
    }
    float sum = acc.reduceLanes(VectorOperators.ADD);
    for (; i < a.length; i++) sum += a[i] * b[i];
    return sum;
}

Results can differ slightly from a scalar loop because the summation order and the fused rounding are different.

Take quiz
What does va.fma(vb, acc) compute per lane?
va + vb * acc
(va + vb) * acc
va * vb + acc
va * vb - acc
Why can the vector dot product differ slightly from a scalar one?
Vector code uses doubles internally
Floats are truncated to ints
The tail is skipped
Summation order and fused rounding differ

30. How do you write branch-free conditional logic with masks?

Compute a mask with compare, then use blend or a masked operation to pick values per lane. Every lane follows the same instruction stream, so there is no branch to mispredict.

// clamp negatives to zero (ReLU style)
var v   = FloatVector.fromArray(SP, in, i);
var neg = v.compare(VectorOperators.LT, 0f);
v.blend(0f, neg).intoArray(out, i);

// same result using max
v.max(0f).intoArray(out, i);

Both sides of a condition are computed for all lanes, so this pays off when the branch is unpredictable and the work is cheap.

Take quiz
What does v.blend(0f, mask) do?
Replaces the lanes selected by the mask with 0f
Replaces unselected lanes with the mask
Sorts the vector by mask value
Throws if any lane is negative
Why is mask-based code branch-free?
The JIT removes all conditionals
All lanes follow the same instructions and the mask selects results
Masks run on a separate thread
The CPU cannot branch on vectors

31. How does fromMemorySegment work with off-heap data?

The Vector API can read and write a MemorySegment from the Foreign Function and Memory API, so you can vectorize data in native or mapped memory without copying it into a Java array. Pass the segment, a byte offset, and a byte order.

try (Arena arena = Arena.ofConfined()) {
    MemorySegment seg = arena.allocate(4096);
    var SP = FloatVector.SPECIES_PREFERRED;
    var v = FloatVector.fromMemorySegment(SP, seg, 0, ByteOrder.nativeOrder());
    v.mul(2f).intoMemorySegment(seg, 0, ByteOrder.nativeOrder());
}

Use ByteOrder.nativeOrder(); any other order forces a byte swap on every access. The offset counts bytes, not elements, and bounds are checked.

Take quiz
Is the offset in fromMemorySegment counted in bytes or elements?
Elements
Lanes
Bytes
Bits
Which byte order avoids extra byte swapping?
Always BIG_ENDIAN
Always LITTLE_ENDIAN on every CPU
Any order costs the same
ByteOrder.nativeOrder()

32. How do you process unsigned bytes such as pixels safely?

Java bytes are signed, and byte lanes wrap on overflow. A pixel value of 200 reads as -56 and adding 100 would wrap. The safe pattern is to widen the lanes first with a zero-extending conversion, compute in a wider type, then narrow again.

ByteVector px = ByteVector.fromArray(ByteVector.SPECIES_64, pixels, i);
// 8 byte lanes -> 8 int lanes, treated as unsigned
IntVector wide = (IntVector) px.convertShape(
        VectorOperators.ZERO_EXTEND_B2I, IntVector.SPECIES_256, 0);
IntVector bright = wide.add(40).min(255);

Narrowing back is done with a conversion such as I2B, and you should clamp first so the value fits.

Take quiz
Why widen byte lanes before adding in pixel code?
Byte lanes are signed and wrap on overflow
Bytes cannot be added on vectors
Widening makes the loop faster by itself
Masks only work on ints
Which kind of conversion treats a byte as 0..255?
A sign-extending one
A zero-extending conversion
A reinterpret as float
A shuffle

33. What is the difference between convert and reinterpret in the Vector API?

Convert changes the values the way a Java cast does: an int lane 3 becomes float 3.0f. Reinterpret keeps the bits and just views them as another type: the int bits of 3 become a tiny denormal float.

convert reinterpret
Value conversion, like (float) i Bit pattern reused, like Float.intBitsToFloat
iv.convert(VectorOperators.I2F, 0) iv.reinterpretAsFloats()
Lane values change Lane values keep their bits

When the lane count would change, such as int to double, convertShape takes a part number to select which piece of the result you want.

Take quiz
What does reinterpretAsFloats() on an IntVector do?
Casts each int value to a float
Rounds lanes to the nearest float
Views the same bits as floats without changing them
Throws if any lane is negative
Which call changes int lane 3 into float 3.0f?
reinterpretAsFloats()
rearrange(shuffle)
blend(0f, mask)
convert(VectorOperators.I2F, 0)

34. How do gather and scatter work in the Vector API?

A gather loads lanes from scattered array positions using an index array. A scatter stores lanes to scattered positions. Both use the indexMap overloads of fromArray and intoArray.

int[] idx = {5, 2, 9, 0, 7, 1, 3, 8};   // 8 lanes
var v = FloatVector.fromArray(SP, data, 0, idx, 0);  // data[idx[j]]
v.mul(2f).intoArray(out, 0, idx, 0);                  // scatter back

Hardware support exists on some CPUs, such as AVX2 gathers and AVX-512 scatters, but it is slower than contiguous loads. Use it when the access pattern truly demands it, and prefer reorganizing data into contiguous arrays when you can.

Take quiz
What does the indexMap overload of fromArray perform?
A gather from positions given by an index array
A sorted copy of the array
A masked load of the tail
A memory-mapped file read
How do gathers compare with contiguous loads?
They are always faster
They are usually slower
They cost exactly the same
They need no bounds checks

35. Why can floating-point reductions in the Vector API differ from a scalar loop?

Floating-point addition is not associative: (a + b) + c can differ from a + (b + c) in the last bits. A scalar loop adds elements in index order. A vector loop adds lane by lane into the accumulator and then combines the lanes, which is a different order.

The reduceLanes contract for floating-point add does not promise a particular order either. So a vector sum can legitimately differ slightly from the scalar sum.

Compare results with a tolerance in tests, not with ==. If you need bit-exact repeatable sums, use a fixed algorithm such as Kahan summation or integer arithmetic, and verify the order.

Take quiz
Why can vector and scalar float sums differ?
Vector code uses 16-bit floats
The JIT drops the last element
Float addition is not associative and the summation order differs
Lanes round toward zero
How should tests compare vector and scalar float results?
With == on every element
By comparing hash codes
By comparing array lengths
With a small tolerance

36. What happens when integer lanes overflow in the Vector API?

Integer lanes wrap around silently, exactly like Java's scalar int, short and byte arithmetic. IntVector.add on Integer.MAX_VALUE and 1 gives Integer.MIN_VALUE in that lane. There is no exception.

Narrow types overflow sooner. Adding two bytes of 100 gives -56. If values can grow, use a wider type, as in pixel and sum code, or clamp with min and max before the add.

Check boundary values in your tests: maximum, minimum, and zero for each lane type.

Take quiz
What does IntVector.add produce for Integer.MAX_VALUE + 1 in a lane?
Integer.MIN_VALUE, with no exception
An ArithmeticException
Integer.MAX_VALUE (saturated)
NaN
Which fix helps when byte sums may overflow?
Use more threads
Widen to a larger lane type before adding
Use SPECIES_MAX
Catch ArithmeticException

37. When should you use a masked tail instead of a scalar tail?

Both handle leftover elements. The difference is the trade-off.

Scalar tail Masked tail
Plain loop after the vector loop One more vector iteration with indexInRange(i, n)
Simple, easy to read and debug No second code path to keep in sync
Slower if the tail is long (up to lanes-1 elements) Masked load and store may be slower on CPUs without native mask support

for (int i = 0; i < n; i += SP.length()) {
    var m = SP.indexInRange(i, n);
    var va = FloatVector.fromArray(SP, a, i, m);
    var vb = FloatVector.fromArray(SP, b, i, m);
    va.add(vb).intoArray(c, i, m);
}

Choose the masked form for short arrays or when one code path matters. Choose the scalar tail when you want the simplest main loop on any hardware. Benchmark both.

Take quiz
What does species.indexInRange(i, n) return?
The number of remaining elements
A shuffle of the last lanes
A mask enabling lanes whose index is below n
The lane count
Which is a downside of a scalar tail?
It cannot handle arrays of length 0
It requires native memory
It forces SPECIES_64
A long tail can run slowly and adds a second code path

38. When should you choose SPECIES_256 instead of SPECIES_PREFERRED?

Use SPECIES_PREFERRED by default; it adapts to the CPU. Pick a fixed species only when the algorithm needs a known lane count.

  • Fixed layouts: processing 8 ints at a time because the data format has blocks of 8.
  • Reproducibility: keeping the summation order identical across machines for floats.
  • Measured wins: on some Intel CPUs 256-bit code runs faster than 512-bit because of clock throttling.

The risk: a fixed species the CPU lacks falls back to slow code. If you hard-code one, verify the machine supports it, or keep a preferred-species path too.

Take quiz
When is a fixed species like SPECIES_256 justified?
When the algorithm needs a specific lane count or reproducible order
Always, because it is faster
Never, the API forbids it
Only on ARM
What is the risk of a hard-coded SPECIES_256?
javac refuses to compile
On unsupported hardware the code falls back to slow execution
It changes array lengths
It disables garbage collection

39. When should you not use the Vector API?

Skip it when it will not pay off or when the cost is not worth it.

  • Scalar code is already fast: if C2 auto-vectorizes the loop, the API adds complexity for nothing.
  • Memory-bound work: if the loop waits on RAM, wider arithmetic does not help.
  • Tiny arrays or cold code: setup and warm-up cost exceed the gain.
  • Irregular access: pointer chasing, heavy branching, small strings.
  • Stability needs: it is an incubating API that can change between releases.

Measure first, and keep a scalar reference version for testing.

Take quiz
When does the Vector API usually give little benefit?
When arrays hold floats
When the CPU supports AVX2
When the loop is limited by memory bandwidth
When the loop is hot
Why keep a scalar reference implementation?
The JVM requires it
To avoid warm-up
Because vectors cannot run in tests
To verify the vector version produces correct results

40. How do you benchmark Vector API code correctly?

Use JMH. A hand-rolled System.nanoTime() test usually measures interpreter and C1 time, not the C2 code you care about.

  1. Add the module to the benchmark JVM: @Fork(jvmArgsAppend = {"--add-modules", "jdk.incubator.vector"}).
  2. Always benchmark against a scalar baseline on the same data.
  3. Use realistic sizes, including ones that exceed the CPU cache.
  4. Return or consume results so the JIT cannot remove the work.
  5. Verify the vector result matches the scalar one before trusting timings.
  6. Repeat on the hardware you deploy on, since results differ across CPUs.
Take quiz
Which tool is the usual choice for benchmarking Vector API code?
JMH
System.currentTimeMillis in main
JUnit assertions
jconsole
How do you pass the incubator module to a JMH fork?
With a @Vector annotation
Through jvmArgsAppend with --add-modules jdk.incubator.vector
With a Maven profile only
It is added automatically

41. How do you troubleshoot slow Vector API code?

Work through the likely causes in order. Most slow cases come from failing to get intrinsified, compiled code.

flowchart TD
  A["Vector code slower than scalar"] --> B{Warmed up and compiled by C2?}
  B -- No --> C["Use JMH with warm-up"]
  B -- Yes --> D{Species static final and supported?}
  D -- No --> E["Fix species, prefer SPECIES_PREFERRED"]
  D -- Yes --> F{Vector objects allocated in profile?}
  F -- Yes --> G["Stop vectors escaping: no fields, collections, or lambdas"]
  F -- No --> H{Memory bound?}
  H -- Yes --> I["Reduce memory traffic, block for cache"]
  H -- No --> J["Check cross-lane ops and gathers in the hot loop"]

Useful tools: a JFR or allocation profiler (look for classes such as Float256Vector being allocated), -XX:+PrintCompilation, JMH profilers like perfasm, and JITWatch. Seeing vector objects allocated in the hot loop is the classic sign the fallback is running.

Take quiz
Which profile finding strongly suggests the slow fallback is running?
High disk I/O
Many ClassNotFoundExceptions
Vector objects such as Float256Vector being allocated in the hot loop
A large number of thread dumps
Which is a sensible first check on slow code?
Rewrite it in JNI
Add synchronized
Reduce the heap size
Confirm it is warmed up and compiled by C2

42. How does the Vector API relate to Project Valhalla?

Valhalla aims to bring value classes (objects without identity) and better generics to Java. Those are exactly the features that would make vectors cheap: a value-class vector could be stored flat, passed in registers, and not need escape analysis to avoid allocation.

Today a vector is a normal heap object that C2 tries to remove. Also, Vector<E> is generic over primitive element types like Float, which today means boxing-like indirection in the type system.

That coupling is why the Vector API will stay in incubation until the necessary Valhalla features are available as previews. After that it can be adapted and promoted.

Take quiz
Which Valhalla feature matters most for the Vector API?
Value classes, which remove object identity and allow flat storage
Virtual threads
Pattern matching
Records
What problem exists today because vectors are normal objects?
They cannot be passed to methods
C2 must eliminate their allocation to reach full speed
They are not thread-safe
They cannot hold floats

43. Why shouldn't you use == or synchronized on Vector instances?

Vector and its subclasses are value-based. They are immutable, and the API makes no promise about identity: two vectors with the same lanes may or may not be the same object, and the JIT may create or remove instances freely.

  • == compares references and can give a surprising answer. Use eq(other) for a per-lane mask or compare the arrays.
  • synchronized on a value-based instance is discouraged and may be flagged, since the identity may vanish.

Because vectors are immutable, they are naturally thread-safe and never need locking. Each operation returns a new vector.

Take quiz
Why is == unreliable on vectors?
Vectors are mutable
Vectors override equals with random values
Vectors are value-based and have no guaranteed identity
== is banned by javac
Why do vectors not need synchronization?
Because the JVM locks them automatically
Because they live on the GPU
They are always thread-local
They are immutable, so sharing them is safe

44. What is the difference between the Vector API and JNI or native SIMD libraries?

JNI or the FFM API lets you call native C code that uses intrinsics or a library like BLAS. The Vector API keeps the logic in Java and relies on the JIT.

Vector API Native SIMD via JNI/FFM
Pure Java, one source for all CPUs Native build per platform and CPU
Inlined and optimized by the JIT Call overhead per invocation, no inlining across the boundary
Limited to what the API exposes Full access to every CPU instruction and tuned libraries
Safe, bounds-checked Memory safety is your responsibility

For standard linear algebra, a tuned native library can still beat Java code. For custom loops in Java projects, the Vector API avoids the packaging and call cost.

Take quiz
Which is a benefit of the Vector API over JNI?
Portable Java code that the JIT can inline
Access to every CPU instruction
Always faster than tuned BLAS
No bounds checks and no warm-up
Which is an advantage of native SIMD libraries?
They run in the interpreter
They can use tuned kernels and the full instruction set
They need no per-platform build
They are bounds-checked by the JVM

45. How does the Vector API stay portable across CPU architectures?

You write against abstract vector types, and the JVM maps them to what the hardware has. On x64 that may be SSE, AVX2 or AVX-512. On AArch64 it may be NEON or SVE. Your source does not change.

Portability here means correctness everywhere, not identical speed everywhere. A species the CPU lacks still works, but through a slow fallback. Some operations map to a single instruction on one CPU and a longer sequence on another.

To keep code portable: use SPECIES_PREFERRED, read lane counts from the species, avoid assuming lane order tricks, and benchmark on each target CPU you care about.

Take quiz
What does 'portable' mean for the Vector API?
The same speed on every CPU
It runs only on x64
The same source runs correctly everywhere, though speed varies by CPU
It compiles to GPU code on ARM
Which practice keeps vector code portable?
Hard-coding 8 lanes
Using inline assembly
Checking os.arch in every loop
Using SPECIES_PREFERRED and reading lane counts from the species

46. How do compress and expand work on a vector?

Both use a mask. compress takes the lanes the mask selects and packs them into the lowest lanes, filling the rest with zeros. expand does the reverse: it spreads the low lanes into the positions the mask selects.

Input [a b c d] Mask [T F T F]
compress [a c 0 0]
expand [a 0 b 0]

// filter: keep positive ints, packed
var v = IntVector.fromArray(SP, in, i);
var m = v.compare(VectorOperators.GT, 0);
int n = m.trueCount();
v.compress(m).intoArray(out, pos, SP.indexInRange(0, n));
pos += n;

That gives a branch-free filter. Both are cross-lane operations, and speed depends on CPU support such as AVX-512.

Take quiz
What does compress do with a mask?
Packs the selected lanes into the lowest lanes
Removes the vector from memory
Sorts lanes by value
Duplicates the selected lanes
Which operation spreads low lanes out to the masked positions?
compress
expand
blend
reduceLanes

47. How do slice and unslice work?

a.slice(k, b) treats a followed by b as one long run of lanes and returns a window of one vector length that starts at lane k. With 4 lanes, a = [0,1,2,3] and b = [4,5,6,7], a.slice(1, b) gives [1,2,3,4].

That is exactly what you need for sliding windows, such as convolution or neighbor differences, without reloading overlapping data. unslice is the inverse operation: it places this vector's lanes into a result at a given offset.

var cur  = IntVector.fromArray(SP, a, i);
var next = IntVector.fromArray(SP, a, i + SP.length());
var shifted = cur.slice(1, next);      // a[i+1 ... i+L]
cur.sub(shifted).intoArray(diff, i);

Take quiz
With a = [0,1,2,3] and b = [4,5,6,7], what is a.slice(1, b)?
[0,1,2,3]
[4,5,6,7]
[1,2,3,4]
[3,2,1,0]
Which task is slice especially suited to?
Sorting an array
Allocating native memory
Creating species
Sliding-window calculations over neighbouring elements

48. How does the Vector API differ from parallel streams?

They exploit different kinds of parallelism and can be combined.

Vector API Parallel streams
Data-level parallelism inside one core (SIMD) Task-level parallelism across cores (threads)
No thread startup or scheduling cost Fork/join overhead, only pays off on large inputs
Needs explicit code and a module flag One method call, no flags
Best for arithmetic over primitive arrays Good for independent per-element tasks of any type

For a very large array you can split the work into chunks across threads and use the Vector API inside each chunk. Remember that memory bandwidth is shared, so adding threads may stop helping.

Take quiz
What kind of parallelism does the Vector API provide?
SIMD, inside a single CPU core
Multiple threads on many cores
Distributed processing across machines
GPU kernels
Can the Vector API and parallel work be combined?
No, they are mutually exclusive
Yes, use threads for chunks and vectors inside each chunk
Only on Windows
Only with virtual threads disabled

49. How do you find the first matching element using SIMD?

Compare a whole vector against the target in one step, then use the mask: anyTrue() tells you if there was a match, and firstTrue() gives its lane. This lets you scan many bytes per iteration and exit early.

static int indexOf(byte[] a, byte target) {
    var SP = ByteVector.SPECIES_PREFERRED;
    int i = 0;
    for (int upper = SP.loopBound(a.length); i < upper; i += SP.length()) {
        var m = ByteVector.fromArray(SP, a, i).compare(VectorOperators.EQ, target);
        if (m.anyTrue()) return i + m.firstTrue();
    }
    for (; i < a.length; i++) if (a[i] == target) return i;
    return -1;
}

Byte vectors have many lanes (32 or 64 on wide CPUs), so this scales well for text and binary scanning.

Take quiz
Which mask method signals that the vector contained a match?
trueCount() == lanes
allTrue()
anyTrue()
lastTrue()
How is the final index computed in the example?
m.trueCount()
i * SP.length()
SP.length() - 1
i + m.firstTrue()

50. How can you optimize memory-bound vector loops?

If a loop is limited by RAM bandwidth, wider arithmetic does little. The aim is to cut memory traffic and keep data in cache.

  1. Fuse passes: do several steps in one pass over the data instead of writing intermediate arrays.
  2. Block for cache: process tiles that fit in L1 or L2, as in matrix multiply.
  3. Use smaller types: floats instead of doubles, or bytes where precision allows, so each load carries more lanes.
  4. Prefer contiguous access: avoid gathers and strided reads.
  5. Use several accumulators: unroll with two or four independent accumulator vectors to hide instruction latency in reductions.

Verify with a benchmark whether you are limited by bandwidth or by compute; the remedy differs.

Take quiz
Which change reduces memory traffic in a multi-step loop?
Fusing the steps into one pass over the data
Adding more threads on the same bus
Using a larger species
Calling reduceLanes more often
Why use several independent accumulator vectors in a reduction?
To reduce array size
To hide instruction latency between dependent adds
To avoid using masks
To make results bit-exact
«
»

Comments & Discussions