Numerical computing for the Java Virtual Machine: N-dimensional arrays with NumPy semantics, built on the Java 25 Foreign Function & Memory API. No Python, NumPy or native libraries are needed at run time.
Status: 0.5.0-alpha implementation candidate (M3.1). Experimental expression execution and real contractions extend the CPU APIs. Reference measurements show substantial AUTO regressions in some cases; see 0.5.0 status. Performance gates remain open and the API remains experimental.
import java.nio.file.Path;
import org.numjvm.NJ;
import org.numjvm.dtype.DType;
import org.numjvm.io.Npy;
import org.numjvm.memory.NJScope;
import org.numjvm.ndarray.NDArray;
import org.numjvm.ndarray.Slice;
NDArray m = NJ.array(new double[]{1, 2, 3, 4, 5, 6}, 2, 3);
System.out.println(m);
// array([[1., 2., 3.],
// [4., 5., 6.]])
NDArray t = m.transpose(); // zero-copy: strides [24, 8] -> [8, 24]
NDArray r = m.reshape(3, 2); // a view when the layout allows it
NDArray s = m.slice(Slice.all(), Slice.step(-1)); // m[:, ::-1]
double x = m.getDouble(1, 2); // 6.0, no allocation
NDArray b = NJ.array(new double[]{10, 20, 30});
NDArray sum = m.add(b); // broadcasting: (2, 3) + (3,)
NDArray big = NJ.greater(m, 3.0); // bool array
NDArray e = NJ.exp(m.transpose()); // any layout; the result keeps it
NDArray cols = NJ.sum(m, 0); // [5., 7., 9.]
double mean = NJ.mean(m).getDouble(); // 3.5
NDArray i8 = NJ.arange(0, 6, 1).astype(DType.INT8);
DType p = NJ.add(i8, NJ.zeros(DType.FLOAT32, 6)).dtype(); // float32, as NumPy 2
m.multiplyInPlace(2.0); // out= m
Npy.write(Path.of("m.npy"), m); // NumPy reads it with np.load
NDArray back = Npy.read(Path.of("m.npy"));
try (NJScope scope = NJ.scope()) { // deterministic memory release
NDArray tmp = scope.zeros(DType.FLOAT64, 1000, 1000);
NDArray tmp2 = NJ.sqrt(tmp); // results of scoped operands stay in the scope
NDArray kept = scope.keep(tmp2.transpose()); // copy that outlives the scope
}Runnable versions: examples/basic-arrays and
examples/memory-scopes:
./gradlew :examples:basic-arrays:run
./gradlew :examples:memory-scopes:run| Area | Contents |
|---|---|
| Dtypes | bool, int8–64, uint8–64, float16, bfloat16, float32, float64, complex64/128 (Complex values) |
| Memory | heap (long[]) and 64-byte aligned native buffers, NJScope (confined or shared), keep(), NJ.memoryStats() |
| Arrays | shape, byte strides (negative and zero allowed), 0-D and empty arrays, invariants checked once per array |
| Element access | getDouble/getLong/getBoolean/getComplex and setters, rank-specialised, allocation-free |
| Views | transpose, permuteDims, swapAxes, slice (NumPy basic indexing), reshape/reshapeView, broadcastTo, real/imag |
| Copies | copy(Order), astype(DType) |
| Ufuncs | arithmetic, comparisons, logical and bitwise, abs, sign, sqrt, exp, log, sin, cos, tanh, rounding: 41 in Ufuncs, with NJ and NDArray shortcuts |
| Promotion | NumPy 2 / Array API 2025.12 lattice, NJ.resultType, NJ.canCast, weak Java long/double/Complex scalars |
| Reductions | sum (pairwise), prod, max, min, mean, any, all over axis lists with keepdims; ufunc.reduce |
| Output | out= arrays with overlap detection, addInPlace and friends; results in the operands' memory order and scope |
| Iteration | IterationPlan (axis reordering and coalescing), StridedIterator, IndexIterator |
| I/O | .npy read, write and memory-map (format 1.0–3.0, both byte orders, C and Fortran order) |
| Printing | toString() matches NumPy's repr |
| Module | Artifact | Contents |
|---|---|---|
numjvm-core |
io.github.stamich:numjvm-core |
dtypes, memory, NDArray, views, ufuncs, reductions |
numjvm-jit |
io.github.stamich:numjvm-jit |
experimental expression execution |
numjvm-io |
io.github.stamich:numjvm-io |
NumPy .npy I/O |
numjvm-vector |
io.github.stamich:numjvm-vector |
optional SIMD kernels |
numjvm-linalg |
io.github.stamich:numjvm-linalg |
real matmul, dot and vecdot; optional SIMD/OpenBLAS |
numjvm-bench |
not published | JMH benchmarks |
numjvm-tests-oracle |
not published | NumPy differential-test fixtures |
M2 adds optional numjvm-vector and numjvm-linalg artifacts. Core remains usable
without Vector API resolution or a native library. Matrix multiplication is real
float32/64, with vector promotion, broadcast batches, arbitrary input strides and
an exact-shape/dtype out variant:
import org.numjvm.linalg.Linalg;
NDArray a = NJ.array(new double[]{1, 2, 3, 4, 5, 6}, 2, 3);
NDArray gram = Linalg.matmul(a, a.transpose()); // [[14, 32], [32, 77]]./gradlew :examples:accelerated-arrays:run
./gradlew :examples:accelerated-arrays:run -PwithoutVector
./gradlew :examples:matmul:runThe optional SIMD provider accelerates selected contiguous and broadcast operations, with scalar fallback elsewhere. Java matmul needs no native dependencies; optional OpenBLAS supports Linux x86-64 LP64 CBLAS. See the setup and backend guide for flags, forced modes, ownership, diagnostics, native validation and consumer examples. The M2 status distinguishes implemented behavior from outstanding release performance evidence.
Experimental real float32/64 Linalg.dot and Linalg.vecdot add vector and
higher-rank contractions, with exact-shape/dtype explicit outputs. See the
contraction guide and run
./gradlew :examples:contractions:run (add -PwithVector for SIMD).
The optional numjvm-jit module traces explicit float32/float64 arithmetic
expressions for block or generated execution. The alpha supports add, subtract,
multiply, divide, negative, absolute, square, and square root; comparisons,
conditional selection, other transcendental functions, reductions, and matmul
inside expressions are not yet supported. See the expression guide
and run ./gradlew :examples:fused-expressions:run. AUTO may select optional
fused SIMD when eligible; lastDecision() identifies the actual strategy.
Reference results show substantial AUTO slowdowns and allocation in some cases;
see the results and
reference protocol.
Requires JDK 25. The Gradle wrapper downloads Gradle, and the build can provision a JDK 25 toolchain if none is installed.
./gradlew build # compile, unit, property and oracle tests, coverage
./gradlew :numjvm-bench:jmh -PjmhArgs="UfuncBenchmark -p size=65536 -prof gc"Coverage reports (JaCoCo) are written to numjvm-*/build/reports/jacoco/test/. Static analysis runs
on SonarCloud; see
CONTRIBUTING.md.
The NumPy oracle needs Python only to regenerate or re-verify its fixtures; see
numjvm-tests-oracle.
- Architecture and design documents
- Architecture decision records
- Benchmark results: M0, M1
- Javadoc:
./gradlew javadoc, output innumjvm-*/build/docs/javadoc
Apache License 2.0, see LICENSE.