Java / Java 25 Vector API Interview questions
Last updated
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
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
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
Eight
Four
Sixteen
Thirty-two
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
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
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
The tenth
The first
The third
It is final, so none
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
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
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
VectorShuffle can be read from and written to a MemorySegment
Addition of a GPU backend
Removal of VectorMask
Vectors became final Java SE classes
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 countelementSize()in bitsvectorBitSize()for total sizeloopBound(n)andindexInRange(i, n)for loop control
Take quiz
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
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
VectorMask
VectorShape
VectorShuffle
VectorSpecies
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
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
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
intoArray
toArray on the species
storeArray
writeTo
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
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
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
A VectorMask with one boolean per lane
A boolean for the whole vector
A new FloatVector of 0s and 1s
A VectorShuffle
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
29
32
24
21
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.
- Scalar tail: after the vector loop, run a normal
forloop over the remaining indexes. It is simple and works everywhere. - 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
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
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, B2Sand similar
v.lanewise(VectorOperators.MUL, w); v.compare(VectorOperators.GE, 0); v.reduceLanes(VectorOperators.MAX);
Take quiz
fromArray
loopBound
lanewise
length
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
Once, after the vector loop finishes
On every iteration
Before the loop starts
Only inside the scalar tail
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
A fixed 1024-bit shape
The size of the CPU cache line
The largest vector size the platform supports
The maximum array length
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
In compilerArgs and in the surefire argLine
Only in pom.xml properties for the license
Only in the compiler plugin
Only in settings.xml
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
add
blend
fma
slice
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
Computing distances between large float arrays
Walking a linked list
Handling HTTP requests
Parsing small config files
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
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
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
reduceLanes
add
mul
compare
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
C2 auto-vectorization
Both of them
The Vector API
Neither
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
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
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
C1
The bytecode interpreter
C2
javac
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
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
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
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
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
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
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
va + vb * acc
(va + vb) * acc
va * vb + acc
va * vb - acc
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
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
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
Elements
Lanes
Bytes
Bits
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
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
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
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
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
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
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
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
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
Integer.MIN_VALUE, with no exception
An ArithmeticException
Integer.MAX_VALUE (saturated)
NaN
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
The number of remaining elements
A shuffle of the last lanes
A mask enabling lanes whose index is below n
The lane count
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 the algorithm needs a specific lane count or reproducible order
Always, because it is faster
Never, the API forbids it
Only on ARM
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 arrays hold floats
When the CPU supports AVX2
When the loop is limited by memory bandwidth
When the loop is hot
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.
- Add the module to the benchmark JVM:
@Fork(jvmArgsAppend = {"--add-modules", "jdk.incubator.vector"}). - Always benchmark against a scalar baseline on the same data.
- Use realistic sizes, including ones that exceed the CPU cache.
- Return or consume results so the JIT cannot remove the work.
- Verify the vector result matches the scalar one before trusting timings.
- Repeat on the hardware you deploy on, since results differ across CPUs.
Take quiz
JMH
System.currentTimeMillis in main
JUnit assertions
jconsole
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
High disk I/O
Many ClassNotFoundExceptions
Vector objects such as Float256Vector being allocated in the hot loop
A large number of thread dumps
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
Value classes, which remove object identity and allow flat storage
Virtual threads
Pattern matching
Records
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. Useeq(other)for a per-lane mask or compare the arrays.synchronizedon 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
Vectors are mutable
Vectors override equals with random values
Vectors are value-based and have no guaranteed identity
== is banned by javac
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
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
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
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
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
Packs the selected lanes into the lowest lanes
Removes the vector from memory
Sorts lanes by value
Duplicates the selected lanes
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
[0,1,2,3]
[4,5,6,7]
[1,2,3,4]
[3,2,1,0]
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
SIMD, inside a single CPU core
Multiple threads on many cores
Distributed processing across machines
GPU kernels
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
trueCount() == lanes
allTrue()
anyTrue()
lastTrue()
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.
- Fuse passes: do several steps in one pass over the data instead of writing intermediate arrays.
- Block for cache: process tiles that fit in L1 or L2, as in matrix multiply.
- Use smaller types: floats instead of doubles, or bytes where precision allows, so each load carries more lanes.
- Prefer contiguous access: avoid gathers and strided reads.
- 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.