JuLC is currently in preview and under active development. Core functionality is available and usable for experimentation and testnet development, but some language features and edge cases are still being implemented and hardened.
APIs and compiler behavior may change between releases. Production use is not yet recommended.
Java UPLC Compiler for Cardano
Pronounced “jool-see” (J-U-L-C), or simply “jules”
Write Cardano smart contracts in Java and compile them to Plutus V3 UPLC. JuLC provides a complete toolchain: a Java-subset compiler, a pluggable VM for local evaluation, a standard library of on-chain operations, and first-class integration with cardano-client-lib.
JuLC supports two VM choices for local evaluation: its pure-Java VM and the
Scalus backend. Both can evaluate JuLC-generated UPLC.
The Java VM integrates with JuLC's complete compiler-target provenance for
explicit protocol-aware evaluation and cost selection. The Scalus adapter also
implements the explicit-target SPI, but Scalus 1.1.0 has no certified ledger
target in JuLC: those calls throw UnsupportedOperationException at the
backend-capability gate before evaluation. Its language-only compatibility API
remains available for V1/V2/V3 evaluation and
uses live supplied cost arrays when configured, subject to the documented
Scalus V1/V2 PV11 cost-field limitations. Scalus remains a valuable independent
cross-check of generated programs. Huge thanks to the Scalus team for building and
open-sourcing a high-quality Plutus VM that made this project possible.
- Java-to-UPLC compiler — write validators in a familiar Java subset, compile to Plutus V3
- Typed ledger access —
ScriptContext,TxInfo,TxOut,Valuewith typed field access and chaining - Records and sealed interfaces — data modeling with pattern matching, switch expressions, and exhaustiveness checking
- Strict typed boundaries — canonical datum/redeemer tags, arities, fields, containers, and productive recursion are checked before validator code
- Instance methods —
list.contains(),value.lovelaceOf(),map.get(),optional.isPresent()and more - Lambda expressions and HOFs —
ListsLib.map(),filter(),foldl(),any(),all(),find(),zip() - Nested loops — for-each and while loops with nesting, multi-accumulator, and break support
- Standard library — 11 libraries: math, lists, maps, values, intervals, crypto, bitwise, output, address, contexts, byte strings
- @NewType — zero-cost type aliases for single-field records
- Tuple2/Tuple3 — generic tuples with auto-unwrapping field access
- Type.of() factories —
PubKeyHash.of(bytes),PolicyId.of(bytes), etc. for ledger hash types - JulcList/JulcMap — typed collection interfaces with IDE autocomplete for on-chain methods
- Multi-validator —
@MultiValidatorfor handling multiple script purposes (mint + spend + withdraw, etc.) in a single compiled script - Annotation processor —
@SpendingValidator,@MintingValidator,@MultiValidator,@Entrypointfor compile-time code generation - Pluggable VM — choose the pure-Java VM or Scalus backend for local UPLC evaluation
- Testkit — test validators locally without a running node
- Gradle plugin — compile validators and bundle on-chain sources as part of your build
- cardano-client-lib integration — deploy and submit transactions with compiled scripts
| Module | Description |
|---|---|
julc-core |
UPLC AST, CBOR/FLAT serialization |
julc-vm |
VM SPI interface |
julc-vm-scalus |
Scalus-based VM backend |
julc-ledger-api |
ScriptContext, TxInfo, and ledger types |
julc-compiler |
Java source to UPLC compiler |
julc-stdlib |
On-chain standard library |
julc-testkit |
Testing utilities for validators |
julc-cardano-client-lib |
cardano-client-lib integration |
julc-gradle-plugin |
Gradle build plugin |
julc-annotation-processor |
Compile-time annotation processor |
julc-verification |
Typed Java security-property annotations and processors |
JuLC compiles a safe subset of Java to UPLC. Key limitations to be aware of:
defaultbranches in switch expressions work as catch-alls for uncovered variants, but prefer explicit cases for all variants of sealed interfaces for clarity@Paramfields: always usePlutusDataas the type for@Paramfields. Other supported types arebyte[],BigInteger,String, records, sealed interfaces, and@NewType. Never usePlutusData.BytesData,PlutusData.MapData,PlutusData.ListData, orPlutusData.IntData— these cause double-wrapping and cross-library type mismatches at runtime- No
Function.apply()— lambdas work with HOFs (list.map(x -> ...),list.filter(...)) but cannot be stored inFunction<T,R>variables and called via.apply() - Immutable variables — variables cannot be reassigned except as loop accumulators in
while/for-each
For the full list of compiler limitations and workarounds, see the Compiler Limitations section in the Getting Started guide.
- julc-helloworld - A simple vesting contract with on-chain and off-chain code, plus tests
- julc-examples - A collection of more complex validators demonstrating various features and patterns
dependencies {
implementation "com.bloxbean.cardano:julc-stdlib:${julcVersion}"
implementation "com.bloxbean.cardano:julc-ledger-api:${julcVersion}"
// Annotation processor -- compiles validators during javac
annotationProcessor "com.bloxbean.cardano:julc-annotation-processor:${julcVersion}"
// Test: choose a VM backend for local evaluation
testImplementation "com.bloxbean.cardano:julc-testkit:${julcVersion}"
testImplementation "com.bloxbean.cardano:julc-vm:${julcVersion}"
// Java VM: supports CompileResult-aware protocol target propagation
testRuntimeOnly "com.bloxbean.cardano:julc-vm-java:${julcVersion}"
// Scalus VM: supported alternative for direct UPLC evaluation and cross-checking
// testRuntimeOnly "com.bloxbean.cardano:julc-vm-scalus:${julcVersion}"
}Only one VM backend is required. If both are present, JuLC selects the Java VM
by default; select Scalus explicitly when an independent evaluation is
desired. The Scalus adapter implements target propagation, but its public
explicit-target gate remains closed until a profile passes the complete
ADR-033 certification matrix.
For detailed dependencies, check the getting started guide or the julc-helloworld example at https://github.com/bloxbean/julc-helloworld.
JuLC currently compiles exactly one profile:
plutus-v3-pv11-uplc-1.1.0. Existing APIs default to that named profile;
“latest” is intentionally not a target, and unknown future protocol versions
fail closed. The target contract and process for adding later protocol versions
are documented in
ADR-031.
var options = new CompilerOptions()
.setTarget(CompilerTarget.PLUTUS_V3_PV11);
var compiler = new JulcCompiler(StdlibRegistry.defaultRegistry(), options);The Gradle plugin accepts the same stable profile ID:
julc {
target = 'plutus-v3-pv11-uplc-1.1.0'
}The reviewed pv11-safe optimization profile is the default and may change
newly compiled script bytes. Select baseline explicitly when reproducing the
pre-optimization lowering. See the
release notes for the
enabled rules, configuration options, measured costs, and hash migration.
For direct annotation-processor configuration, pass
-Ajulc.target=plutus-v3-pv11-uplc-1.1.0. Supporting a later protocol version
will add a separately pinned compiler target and feature matrix; it will not
silently change this default.
0.1.0-pre16
ext.julcVersion = '0.1.0-pre16'Snapshot versions include the Git commit hash for traceability, e.g. 0.1.0-055d17f-SNAPSHOT.
Current snapshot version: 0.1.0-055d17f-SNAPSHOT. Check here for the latest snapshot commit ID: https://github.com/bloxbean/julc/actions/workflows/snapshot.yml
To use snapshots, add the Sonatype snapshot repository:
Gradle
repositories {
mavenCentral()
maven {
url "https://central.sonatype.com/repository/maven-snapshots"
}
}Maven
<repositories>
<repository>
<id>snapshots-repo</id>
<url>https://central.sonatype.com/repository/maven-snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>Then use the snapshot version in your dependencies:
implementation "com.bloxbean.cardano:julc-stdlib:${julcVersion}"@SpendingValidator
public class VestingValidator {
record VestingDatum(PubKeyHash beneficiary, BigInteger deadline) {}
@Entrypoint
static boolean validate(VestingDatum datum, PlutusData redeemer, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
// Check that the beneficiary signed the transaction
boolean signed = txInfo.signatories().contains(datum.beneficiary());
// Check that the deadline has passed (lower bound of valid range > deadline)
// Just a dummy check to demonstrate using the datum's deadline field.
boolean pastDeadline = datum.deadline().compareTo(BigInteger.ZERO) > 0;
return signed && pastDeadline;
}
}@MintingValidator
public class TokenPolicy {
sealed interface Action permits Mint, Burn {}
record Mint(BigInteger amount) implements Action {}
record Burn() implements Action {}
@Entrypoint
static boolean validate(Action action, ScriptContext ctx) {
TxInfo txInfo = ctx.txInfo();
return switch (action) {
case Mint m -> m.amount().compareTo(BigInteger.ZERO) > 0 && !txInfo.signatories().isEmpty();
case Burn b -> true;
};
}
}@MultiValidator
public class TokenManager {
@Entrypoint(purpose = Purpose.MINT)
static boolean mint(PlutusData redeemer, ScriptContext ctx) {
return !ctx.txInfo().signatories().isEmpty();
}
@Entrypoint(purpose = Purpose.SPEND)
static boolean spend(PlutusData redeemer, ScriptContext ctx) {
return true;
}
}During a Gradle build, the @SpendingValidator and @MintingValidator annotated classes are compiled to UPLC and saved as JSON files in META-INF/plutus/ inside the JAR. You can load these compiled scripts at runtime using JulcScriptLoader:
PlutusV3Script script = JulcScriptLoader.load(VestingValidator.class);
// Use `script` for transaction building with cardano-client-libvar stdlib = StdlibRegistry.defaultRegistry();
var compiler = new JulcCompiler(stdlib);
var result = compiler.compile(javaSource);
if (!result.hasErrors()) {
Program program = result.program();
CompilerTarget target = result.target();
// Ready for serialization and on-chain deployment with explicit provenance
}// Java VM: preferred target-aware evaluation. This passes result.target()
// through to the VM. With only Scalus present, the implemented target-aware
// path currently throws UnsupportedOperationException because no Scalus
// 1.1.0 target
// has passed ADR-033 certification.
var evalResult = ValidatorTest.evaluate(result, datum, redeemer, scriptContext);
// Scalus compatibility cross-check: use the language-only API. Configured
// V1/V2/V3 calls receive live protocol cost arrays; see ADR-033 for the audited
// V1/V2 PV11 fields that Scalus 1.1.0 reference-fills or ignores.
// var scalus = JulcVm.create("Scalus");
// var evalResult = scalus.evaluateWithArgs(
// result.program(), List.of(datum, redeemer, scriptContext));
assertTrue(evalResult.isSuccess());Passing the CompileResult lets julc-testkit hand the exact compiler target
to the VM. The testkit's raw Program overload assumes JuLC's current
V3/PV11 compiler target when provenance is unavailable. The Scalus adapter
implements this explicit-target testkit path, but throws
UnsupportedOperationException while its certification set is empty. Use the
Java VM for canonical target-aware
evaluation and Scalus's language-only overloads for an independent
compatibility cross-check. See
ADR-033.
- Java 25+
- Gradle 9+
| Guide | Description |
|---|---|
| Getting Started | Comprehensive guide: validators, data modeling, collections, control flow, stdlib, testing, deployment |
| API Reference | All supported types, operators, methods, and ledger access |
| Standard Library Guide | All 13 stdlib libraries with usage examples |
| Advanced Guide | Low-level PlutusData patterns, type casting, raw list/map manipulation, debugging |
| For-Loop Patterns | For-each, while, nested loops, multi-accumulator, break |
| Library Developer Guide | Writing @OnchainLibrary modules and PIR API |
| Troubleshooting | Every compiler error, common mistakes, and FAQ |
| Compiler Developer Guide | Internal architecture for compiler contributors |
MIT