Skip to content

[FEA] Publish self-contained Java classifier JARs with a statically linked libcuopt, as cuDF does #1817

Description

@ramakrishnap-nv

Is your feature request related to a problem? Please describe.

The Java bindings (#1524) have no installation path. Nothing is published — no jar, no libcuopt_jni.so — so a user must build the JNI shim from source. That needs libcuopt, its headers, a C++20 compiler, CMake, Ninja, Maven and a JDK.

Conda is effectively mandatory, and not by choice: the JNI build needs the exact rmm and raft headers libcuopt was compiled against, because rmm encodes its version in an inline namespace (rmm::_RMM_26_10). A mismatched copy links cleanly and then fails at dlopen with an undefined symbol. Those headers only ship as conda packages, so a pip wheel is not sufficient.

Verified end to end in a clean nvidia/cuda:13.0.3-base-ubuntu24.04 container: miniforge -> conda env -> build_native.sh -> mvn package -> compile and run a user program. It works, but that is the floor for "hello world".

Describe the solution you'd like

Adopt the cuDF model: publish self-contained per-classifier JARs that bundle a statically linked native library, so a user needs only a Maven dependency and a driver.

cuDF did this in rapidsai/cudf#23261 (merged 2026-07-21):

Link against a static libcudf built from source per CUDA version rather than a conda shared libcudf.

It builds with -DBUILD_SHARED_LIBS=OFF and splits the work into three composable stages — static native build, per-classifier JAR packaging, Maven-repo gather.

The result is genuinely self-contained. libcudf.so inside the published jar has these DT_NEEDED entries and nothing else:

librt, libz, libdl, libpthread, libstdc++, libm, libgcc_s, libc, ld-linux

No libcudart, no librmm, no conda. Published classifiers on Maven Central for ai.rapids:cudf:26.08.1:

cudf-26.08.1-cuda12.jar          707.9 MB
cudf-26.08.1-cuda12-arm64.jar    702.6 MB
cudf-26.08.1-cuda13.jar          321.5 MB
cudf-26.08.1-cuda13-arm64.jar    317.5 MB

For cuOpt this would also defuse #1703 as a distribution hazard. libcuopt_jni.so currently reaches through the private pdlp/cuopt_c_internal.hpp and is coupled to a specific libcuopt build. That is only dangerous because the user supplies libcuopt independently; if both halves are built and shipped together, a cuOpt point release cannot break the jar. It does not remove the need for #1703, but it removes the failure mode.

Describe alternatives you've considered

The cuVS model — a pure-Java jar. com.nvidia.cuvs:cuvs-java is 1.4 MB, ships a NativeDependencyLoader, and expects libcuvs from conda/pip/system. Simpler and far smaller, but it leaves the user to supply and match the native library, which is exactly the fragile configuration given #1703.

Reasonable as a stopgap; poor as an end state.

Additional context

Size is the open risk, and it is specific to cuOpt. libcuopt.so is 554 MB shared with 29 DT_NEEDED entries:

dependency size
libcublas 517 MB
libnccl 279 MB
libcusparse 155 MB
libcudss 67 MB
libgrpc / protobuf / abseil ~25 MB

A naive static build would likely exceed cuDF's 708 MB. Maven Central documents a 1 GB limit per uploaded bundle ("You can upload an archive up to 1GB in size"), applying to the whole archive rather than a single file.

The scoping work needed is already in flight, and this issue depends on it:

NCCL (distributed multi-GPU PDLP) and the gRPC server stack are not needed by a Java jar at all, which is ~300 MB of the total.

Publishing infrastructure is being rebuilt upstream and we should adopt it rather than write our own:

Worth noting when planning: cuDF's java-publish job is currently skipped in every recent build.yaml run (gated on release builds) and #385 is not started, so that pipeline has not yet run for a real release. The 26.08.1 artifacts on Central came from the legacy Spark RAPIDS Jenkins process. Its assemble_maven_repo.sh gathers all classifiers into a single Maven repo tree published as one bundle, which for 26.08.1 totals 2.69 GB against the documented 1 GB ceiling — unresolved, and raised upstream.

Proposed scope

  1. A static libcuopt build (BUILD_SHARED_LIBS=OFF), LP-only where build(cmake): split libcuopt into cuopt_base / cuopt_routing / cuopt_lp component libs #1622 allows, excluding routing, NCCL and the gRPC server.
  2. Per-classifier JAR packaging: cuda12 / cuda13 x x86_64 / arm64.
  3. A Maven-repo gather step producing the layout the shared publish workflow consumes.
  4. Measure the resulting jar. If it approaches 1 GB, that is the signal to narrow scope further rather than to proceed.

Contributes to #1535.

Metadata

Metadata

Assignees

Labels

awaiting responseThis expects a response from maintainer or contributor depending on who requested in last comment.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions