CPython4J is Java-first. The JVM owns the process, lifecycle, configuration, contracts, and callbacks.
Python is the guest runtime. Python code may implement Java-defined contracts and may call explicitly exposed Java host contracts.
The project runs real CPython through the Python C ABI. It is not a Python implementation on the JVM. It is not GraalPy. It is not Jython. It is not WASM.
The Java side calls native code through Java's Foreign Function & Memory API. No JNI. No compiled C helper or shim.
Host modules and callbacks are built purely through FFM: PyMethodDef/PyModuleDef structs laid out via MemorySegment, function pointers created via Linker.nativeLinker().upcallStub(). The only native library is libpython itself.
The acceptable native boundary is:
Java FFM <-> libpython
The unacceptable native boundary is:
Java <-> JNI wrapper <-> CPython
Java <-> compiled C helper <-> CPython
The runtime must not use java.lang.reflect.Proxy, Method.invoke(), or any other reflective mechanism. All type information and method dispatch must be resolved at compile time through annotation processors that generate concrete classes. This matches the QuickJs4J approach.
The primary API is not py.call("name", ...).
The primary API is:
MyApi api = engine.load(MyApi.class, PythonSource.file(path));Python modules implement Java interfaces. Java host interfaces can be exposed as Python modules. The annotation processor generates concrete implementation classes at compile time.
Data crosses the boundary by value:
- primitives directly;
- non-primitive structured values through JSON-shaped conversion.
There is no identity-preserving object graph conversion.
Python must not see arbitrary Java classes, methods, or fields.
Allowed:
from host import find_user
user = find_user("u1")Not allowed:
from java.time import LocalDate
obj.someJavaMethod()Python state should stay in Python. Java interacts with Python through Java-defined contracts.
No public PyRef should be present in v1. If Python needs state, the Python module owns that state.
Python environment management is a product feature, not a footnote.
The preferred flow is uv project support. Existing .venv and explicit configuration are supported as escape hatches. A battery-included mode bundles pyproject.toml inside JAR resources so that uv can bootstrap Python and all dependencies on first run.
Embedded CPython is trusted code. Native Python extensions can crash the JVM process. This project is not a security sandbox.
The core should remain small:
- engine lifecycle;
- contract loading;
- host exposure;
- primitive and JSON conversion;
- callback bridge;
- uv/venv environment discovery.
Convenience features such as annotations, custom converters, record naming policies, Gradle plugins, or typed diagnostics can live in optional modules.
It should be possible to ship a Java application that embeds Python workloads as a self-contained artifact. The library should support bundling pyproject.toml and Python sources inside JAR resources. On first run, uv bootstraps CPython and all dependencies into a local cache. After the first run, the cached environment is reused.
The motivating use case is Docling: a Python document AI library with many transitive dependencies including native extensions. A Java team should be able to mvn package and distribute a JAR that runs Docling without requiring end users to install Python.