Java¶
Repository: github.com/me60732/tulip_rs_java
The Java binding uses the Panama FFM API (java.lang.foreign, final since JDK 22)
directly against the shared tulip_rs_ffi native library — no JNI, no
code generation, zero third-party dependencies. It exposes every indicator, both
SIMD modes, state streaming, serialization, and candlestick patterns.
Installation¶
From Maven Central — the native library comes with the dependency: an OS-activated profile in the published pom pulls the matching platform classifier jar (linux-amd64/arm64, darwin-amd64/arm64, windows-amd64) containing the portable-baseline binary, extracted automatically at first load.
<dependency>
<groupId>io.github.me60732</groupId>
<artifactId>tulip-rs-java</artifactId>
<version>0.2.10</version>
</dependency>
Gradle: implementation 'io.github.me60732:tulip-rs-java:0.2.10'
Requirements: JDK 22+ (no --enable-preview needed; tested on Temurin 25).
Build from source (CPU-tuned) — mirroring the other bindings, a local Rust
toolchain + -C target-cpu=native beats the portable baseline:
git clone https://github.com/me60732/tulip_rs_java
cd tulip_rs_java
./bootstrap.sh --source # clones ../tulip_rs_ffi and builds with native CPU tuning
./build.sh # -> out/ (library) + out-examples/
Quick Examples¶
Every indicator follows the same shape:
import org.tuliprs.*;
import org.tuliprs.indicators.Sma;
double[] close = {81.59, 81.06, 82.87, 83.00, 83.61,
83.15, 82.84, 83.99, 84.55, 84.36};
// inputs: double[][] (one array per input series) | options: double[]
Outcome oc = Sma.indicator(new double[][] {close}, new double[] {5.0});
try (Result res = oc.result(); State st = oc.state()) {
double[] sma = res.toDoubleArray(0); // zero-copy view over Rust memory
// Streaming continuation — feed new bars without reprocessing history
Result br = st.batch(new double[][] {newBars});
try (br) { ... }
}
Memory model¶
Outputs and streaming state are Rust-allocated, so the JVM cannot reclaim them:
wrap Result / State / SimdResult in try-with-resources and every native
free runs automatically, in the contractual order. All close()s are
idempotent and a java.lang.ref.Cleaner backstop catches leaks — the end user
never writes a free.
State persistence¶
byte[] blob = st.serialize(Format.BINCODE); // or Format.JSON
State restored = Sma.deserializeState(blob); // blobs are self-describing
State snapshot = st.duplicate(); // Rust-Clone value semantics
SIMD¶
// N assets (2, 4, 8, 16) through one option set, in one CPU pass:
try (SimdResult sim = Sma.simdByAssets(new double[][][] {{a1}, {a2}, {a3}, {a4}},
new double[] {5.0}, null)) {
for (int i = 0; i < sim.numResults(); i++) {
double[] assetI = sim.toDoubleArray(i, 0); // lane i, output row 0
}
}
// One asset through N option sets:
try (SimdResult sim = Sma.simdByOptions(new double[][] {close},
new double[][] {{2}, {5}, {8}, {10}}, null)) { ... }
Indicator info¶
Info info = Sma.info(); // name, fullName, inputs, options, outputs,
// optionalOutputs, type, displayGroups
long need = Sma.minData(new double[] {5.0});
Constants Sma.INPUTS, Sma.OPTIONS, Sma.ID (the FFI's FNV-1a32 indicator
id used by serialization) are exposed on every facade.
Candlestick¶
Pattern detection returns per-bar pattern ids/names instead of f64 rows, takes
an int forecast filter (CandlePattern.FORECAST_BULLISH_REVERSAL and
friends), and has no SIMD variants. See
examples/org/tuliprs/examples/CandlestickExample.java in the repository.