Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions .github/workflows/java-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: Java release
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved

# Cut a release by pushing a tag, e.g.: git tag java-v0.13.0 && git push origin java-v0.13.0
on:
push:
tags:
- 'java-v*'

# Deploy goes to Maven Central via the Sonatype plugin (repo secrets), so the
# job needs no write scope on the GitHub token — only read to check out.
permissions:
contents: read

env:
CARGO_TERM_COLOR: always

jobs:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
# Build the convex-ffi cdylib for every platform classifier.
natives:
name: cdylib (${{ matrix.classifier }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: true
matrix:
include:
- { runner: ubuntu-latest, target: x86_64-unknown-linux-gnu, classifier: linux-x86_64, lib: libconvex_ffi.so }
- { runner: ubuntu-latest, target: aarch64-unknown-linux-gnu, classifier: linux-aarch64, lib: libconvex_ffi.so, cross: true }
- { runner: macos-latest, target: x86_64-apple-darwin, classifier: darwin-x86_64, lib: libconvex_ffi.dylib }
- { runner: macos-latest, target: aarch64-apple-darwin, classifier: darwin-aarch64, lib: libconvex_ffi.dylib }
- { runner: windows-latest, target: x86_64-pc-windows-msvc, classifier: windows-x86_64, lib: convex_ffi.dll }
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
targets: ${{ matrix.target }}

- name: Install aarch64 cross linker
if: matrix.cross
run: |
sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> "$GITHUB_ENV"

- name: Build cdylib
run: cargo build -p convex-ffi --release --target ${{ matrix.target }}

- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ${{ matrix.classifier }}
path: target/${{ matrix.target }}/release/${{ matrix.lib }}
if-no-files-found: error

# Stage every cdylib into the jar resources and publish to Maven Central.
deploy:
name: Publish to Maven Central
needs: natives
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

- name: Download native libraries
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
path: staging

- name: Stage natives into resources
# download-artifact lays out staging/<classifier>/<lib>; mirror that
# straight into the resources/native tree the loader reads.
run: |
mkdir -p bindings/java/src/main/resources/native
cp -r staging/* bindings/java/src/main/resources/native/

- name: Set up JDK 22
uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
with:
distribution: temurin
java-version: '22'
cache: maven
server-id: central
server-username: MAVEN_CENTRAL_USERNAME
server-password: MAVEN_CENTRAL_PASSWORD
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
gpg-passphrase: MAVEN_GPG_PASSPHRASE

- name: Deploy
working-directory: bindings/java
env:
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
run: mvn -B -Prelease deploy
68 changes: 68 additions & 0 deletions .github/workflows/java.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Java bindings
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved

on:
push:
branches: [main]
paths:
- 'bindings/java/**'
- 'crates/convex-ffi/**'
- 'crates/convex-analytics/**'
- 'crates/convex-bonds/**'
- 'crates/convex-core/**'
- 'crates/convex-curves/**'
- 'crates/convex-math/**'
- '.github/workflows/java.yml'
pull_request:
branches: [main]
paths:
- 'bindings/java/**'
- 'crates/convex-ffi/**'
- 'crates/convex-analytics/**'
- 'crates/convex-bonds/**'
- 'crates/convex-core/**'
- 'crates/convex-curves/**'
- 'crates/convex-math/**'
- '.github/workflows/java.yml'
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

# Build + test only; no repo writes.
permissions:
contents: read

env:
CARGO_TERM_COLOR: always

jobs:
# Linux x86_64 build + test. The release workflow extends this into a full
# OS/arch matrix and assembles every cdylib into the natives jar.
verify:
name: Build native + mvn verify (linux-x86_64)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
with:
persist-credentials: false

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Cache cargo
uses: Swatinem/rust-cache@v2

- name: Build convex-ffi cdylib
run: cargo build -p convex-ffi --release

- name: Stage native library
run: |
dest=bindings/java/src/main/resources/native/linux-x86_64
mkdir -p "$dest"
cp target/release/libconvex_ffi.so "$dest/"

- name: Set up JDK 22
uses: actions/setup-java@v4
Comment on lines +41 to +61

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/java.yml"
echo "== File exists? =="
ls -l "$FILE"

echo "== Lines 1-120 (for context) =="
cat -n "$FILE" | sed -n '1,120p'

echo "== uses: entries with line numbers =="
rg -n "^\s*-\s*uses:\s*" "$FILE" || true

echo "== All uses: occurrences (broader) =="
rg -n "uses:\s*" "$FILE" || true

Repository: sujitn/convex

Length of output: 2210


Pin the third-party GitHub Actions to full commit SHAs.

.github/workflows/java.yml uses tag-based actions (actions/checkout@v6, dtolnay/rust-toolchain@stable, Swatinem/rust-cache@v2, actions/setup-java@v4) rather than immutable commit SHAs, leaving the workflow exposed to supply-chain drift.

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 29-29: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 29-29: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 32-32: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 35-35: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 47-47: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/java.yml around lines 29 - 47, Replace tag-based GitHub
Action refs in the workflow (e.g., actions/checkout@v6,
dtolnay/rust-toolchain@stable, Swatinem/rust-cache@v2, actions/setup-java@v4)
with the corresponding immutable full commit SHAs; locate each uses: entry in
.github/workflows/java.yml and update the ref to the specific commit SHA from
the action's repository (e.g., actions/checkout@<full-sha>) so the workflow is
pinned to exact versions and future tag drift cannot change behavior.

with:
distribution: temurin
java-version: '22'
cache: maven

- name: Maven verify
run: mvn -B -f bindings/java/pom.xml verify
7 changes: 7 additions & 0 deletions bindings/java/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Maven build output
target/

# Native libraries are built/staged by CI, not committed.
src/main/resources/native/**/*.so
src/main/resources/native/**/*.dylib
src/main/resources/native/**/*.dll
131 changes: 131 additions & 0 deletions bindings/java/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Convex Java

JVM bindings for the [Convex](../../README.md) fixed-income analytics library.

Java users get the same analytics as the Rust API — bonds, curves, pricing,
risk, spreads, and the hedge advisor — through an idiomatic typed API
(builders, `BigDecimal`/`LocalDate`, immutable result records, `AutoCloseable`
handles). The JSON-RPC boundary to the native library is fully hidden.

## How it works

```
com.convex ← typed public API (builders, ConvexAnalytics, HedgeAdvisor)
com.convex.internal ← Panama FFM binding + Jackson envelope codec (hidden)
crates/convex-ffi ← the native cdylib (C ABI), built from the Rust workspace
```
Comment on lines +12 to +16

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Label the architecture diagram fence.

This unlabeled fence triggers markdownlint and is easier to read if marked as plain text.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 12-12: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bindings/java/README.md` around lines 12 - 16, Label the unlabeled fenced
block that lists the packages (the block containing "com.convex",
"com.convex.internal", and "crates/convex-ffi") by adding a language identifier
to the opening fence (e.g. change ``` to ```text or ```plain) so the README's
code fence is explicitly marked as plain text and satisfies markdownlint; leave
the block content unchanged and only modify the opening fence token.


The binding calls the native library through Java's **Foreign Function & Memory
API** (Project Panama). There is **no hand-written JNI / C shim** — Java binds
directly to the cdylib's `char*`-in / `char*`-out C functions by symbol name.

## Requirements

- **JDK 22 or newer** (the FFM API is finalized in 22).
- At runtime, native access must be granted:
`--enable-native-access=ALL-UNNAMED` (the Maven build sets this for tests).
- The native `convex-ffi` cdylib for your platform, bundled under
`src/main/resources/native/<classifier>/` (see below). Versions track the Rust
workspace (currently `0.13.0`).

## Building

```sh
# 1. Build + stage the native library for your platform classifier
# (linux-x86_64, linux-aarch64, darwin-x86_64, darwin-aarch64, windows-x86_64)
cargo build -p convex-ffi --release
mkdir -p bindings/java/src/main/resources/native/linux-x86_64
cp target/release/libconvex_ffi.so \
bindings/java/src/main/resources/native/linux-x86_64/

# 2. Build + test the jar
mvn -f bindings/java/pom.xml verify
```

`NativeLoader` extracts the cdylib matching the running OS/arch from the jar at
startup, so one published artifact carries every platform.

## Usage

```java
import com.convex.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;

var settle = LocalDate.of(2025, 4, 15);

try (Bond bond = FixedRateBond.builder()
.cusip("037833100")
.couponRate(new BigDecimal("0.05"))
.frequency(Frequency.SEMI_ANNUAL)
.issue(LocalDate.of(2025, 1, 15))
.maturity(LocalDate.of(2035, 1, 15))
.dayCount(DayCount.THIRTY_360_US)
.currency(Currency.USD)
.build();
YieldCurve curve = YieldCurve.discrete()
.referenceDate(LocalDate.of(2025, 1, 15))
.point(2.0, 0.040).point(5.0, 0.042).point(10.0, 0.045)
.interpolation(Interpolation.MONOTONE_CONVEX)
.build()) {

// Pricing & risk
var price = ConvexAnalytics.price(bond, settle, Mark.cleanPrice(new BigDecimal("99.5")));
var risk = ConvexAnalytics.risk(bond, settle, Mark.cleanPrice(new BigDecimal("99.5")), curve, 2, 5, 10, 30);
System.out.printf("YTM %.4f, DV01 %.2f%n", price.ytmDecimal(), risk.dv01());

// Hedge advisor
RiskProfile pos = HedgeAdvisor.positionRisk()
.bond(bond).settlement(settle).mark(Mark.cleanPrice(new BigDecimal("99.5")))
.notionalFace(new BigDecimal("10000000")).curve(curve).curveId("USD.SOFR")
.keyRateTenors(2, 5, 10, 30)
.compute();

HedgeProposal futures = HedgeAdvisor.durationFutures(pos, curve, settle);
HedgeProposal swap = HedgeAdvisor.swap(pos, curve, settle, Constraints.none());
ComparisonReport report = HedgeAdvisor.compare(pos, List.of(futures, swap), Constraints.none(), true);
System.out.println(report.narrative().orElse(""));
}
```

## Thread-safety

The native registry and analytics are safe for concurrent use, and the FFM
method handles are immutable, so the API may be called from multiple threads.
(The `convex-ffi` registry clones objects out under the lock before running
analytics, so concurrent calls never deadlock.)

## Status / not yet wired

The native FFI does not yet expose every Rust capability. Currently missing
(same DTO + dispatch pattern when added — see
`crates/convex-analytics/src/dto.rs`):

- Convention/calendar introspection (`get_convention_options` analog)
- YAS settlement invoices, VaR, Hull-White calibration, piecewise bootstrap
- FRN projection / cap-floor analytics

Packaging note: the binding ships as an automatic module (works on the
classpath). A `module-info.java` is intentionally omitted for now; named-module
consumers should grant `--enable-native-access` to the unnamed module.

## Releasing to Maven Central

Publishing is driven by the `release` Maven profile (sources + javadoc jars,
GPG signing, and the Sonatype Central Portal plugin) and the
`.github/workflows/java-release.yml` workflow, triggered by a `java-v*` tag.

The workflow builds the cdylib for every platform classifier in a matrix,
stages them all under `src/main/resources/native/`, then runs
`mvn -Prelease deploy` so the single jar carries every platform.

Required repository secrets:

| Secret | Purpose |
|---|---|
| `MAVEN_CENTRAL_USERNAME` / `MAVEN_CENTRAL_PASSWORD` | Central Portal user token |
| `MAVEN_GPG_PRIVATE_KEY` | ASCII-armored signing key |
| `MAVEN_GPG_PASSPHRASE` | passphrase for that key |

To cut a release: `git tag java-v0.13.0 && git push origin java-v0.13.0`.
Loading
Loading