← SuperJ Manual

Getting Started with SuperJ

What SuperJ is

SuperJ is a systems programming language with Java's syntax, compiled ahead-of-time to native code through LLVM. You write what looks like ordinary Java — classes, interfaces, generics, enums, exceptions — and get a single self-contained native binary with no JVM, no JIT warm-up, and no garbage collector.

It is not a subset of Java. It keeps the parts of Java that make it pleasant and productive, drops the parts that fight against predictable native performance, and adds a layer of systems-level capability that Java doesn't have. The result reads like Java but behaves like C — hence super Java.

Why "super", not "subset"

A subset would only take away. SuperJ takes away and gives back:

Kept — the good parts of Java Classes and interfaces, single inheritance with multiple interfaces, generics (bounded type parameters, wildcards, the diamond <>), enums (with per-constant bodies), instanceof pattern matching with flow scoping, method references (::), varargs, try/catch/finally, @Override, the full operator and control-flow set, and a familiar String/collections feel.

Dropped — the parts that fight native performance or determinism Threads, locks, synchronized, volatile, java.util.concurrent (SuperJ is single-threaded by design — scale with processes, not threads); reflection, ClassLoader, and JPMS modules (everything is resolved at compile time); lambdas and non-static inner classes (no hidden closure machinery — method references and static nested classes cover the ground); autoboxing and checked exceptions; finalize().

Added — the modern, systems-level upgrades plain Java lacks

Design philosophy

Einstein is credited with the line that any smart fool can build a complex system, but it takes a real genius to make a system as simple as possible — and no simpler. SuperJ's design is the working-out of that principle for one language. The industry "obvious goods" — threads for concurrency, a borrow checker for safety, source-level ecosystem integration — are each a complex solution to a real problem, and in each case a simpler solution exists that removes the problem instead of managing it:

In each case the complex solution is what you reach for when you assume the problem must be managed; the simpler solution is what you get when you notice the problem can be removed. Complexity isn't just harder to use — it's where the undecidable bugs, the nondeterminism, and the monolingual lock-in live. Simpler isn't just nicer; it's more likely to be correct. The four principles below are the concrete instances of that idea.

  1. Single-threaded determinism. Removing concurrency isn't a limitation — it's the point. One event-loop thread per process means no locks, no data races, no scheduler jitter, and code you can reason about. Scale out with processes (SO_REUSEPORT), not shared-memory threads.
  2. No GC, no pauses. Arena allocation reclaims memory deterministically. The measured payoff is a near-flat latency curve: the HTTP server holds p99 ≈ p50 under load — the kind of tail behavior GC'd JVMs chase with great effort.
  3. Native performance is the baseline. AOT compilation to LLVM, zero- allocation hot paths, and the systems SDK put SuperJ in the same class as the fastest published frameworks — the plaintext HTTP server out-throughputs the TechEmpower Rust/C plaintext champions on equal hardware, while running a full HTTP parse per request.
  4. Familiarity lowers the cost of all the above. A Java developer is productive immediately; the new capabilities are opt-in, not prerequisites.

Install

SuperJ ships as a prebuilt binary distribution — download the tarball for your platform from the Download page, extract, and set two environment variables. The only host prerequisite is a C linker (clang/LLVM 22.x on PATH); everything else ships in the package.

Download & extract:

tar xzf superj-v1.3.0-<platform>-community.tar.gz   # macos-arm64 | linux-x86_64 | linux-arm64
export SJ_HOME="$PWD/superj"
export PATH="$SJ_HOME/bin:$PATH"

Open a new shell (or source your profile), then:

superj --version        # 1.3.0 (community)

SDK variants

SuperJ ships in three flavours that differ in how the SDK is delivered. Only the community variant is available for download today. Your code is identical across all three — only the link path changes.

The three variants:

VariantWhat you getLink pathTradeoff
communityPrebuilt static archive (libsuperj_sdk.a)compiler links the archive; clang links the final binaryFastest link; no cross-module whole-program optimization. Smallest install. Available for download.
enterprisePrebuilt SDK IR (sdk.ll) + runtime objectscompiler llvm-links sdk.ll with your IR; clang -O3 the whole programWhole-program optimization across SDK + user code (global inlining, dead-code elimination); slower link, better runtime perf.
vipSDK source (.sj files, no prebuilt artifacts)compiler compiles SDK + your sources together (--sdk-source); clang linksMaximum optimization + full source for audit/modification; slowest build.

Your code is identical across all three — superj compile auto-detects the installed mode from $SJ_HOME/sdk, so you normally don't think about it:

superj compile app.sj                 # auto-detect the installed mode, emit IR
superj compile app.sj --link          # … and link an executable (always -O3)
superj compile app.sj --link --enterprise   # force whole-program LTO (needs sdk.ll)

superj compile --link always passes -O3 to clang (unless --debug). Explicit --sdk-path <dir> (community), --sdk-source <dir> (VIP), and --enterprise override auto-detection. A program that imports nothing from sj.* is fully standalone — no SDK is linked at all.

Optimization flags

FlagEffect
(default)-O3 — always, unless --debug
--enterpriseWhole-program LTO: llvm-link sdk.ll + clang -O3 the merged module (cross-CU inlining/devirt)
--no-bounds-checkDisable array bounds checks (hot numeric loops; ~1.1-1.5x speedup)
--simd <level>sse2 (default, x86), avx2 (x86 256-bit), none (disable). ARM NEON is always on (flag is a no-op).
--target-cpu <cpu>clang -mcpu=<cpu> (e.g. native, apple-m2)
--debug-O0 -g (1:1 debug line mapping; no optimization)

Maximum performance:

superj compile app.sj --sdk-source "$SJ_HOME/sdk/sj" --enterprise --link \
    --no-bounds-check --simd avx2 --target-cpu native --output app
# or via the build system:
superj build --release --enterprise

Which variant am I using?

Check what's in $SJ_HOME/sdk/build/:

ls $SJ_HOME/sdk/build/

Switching variants means re-running the other installer.

Hello, SuperJ

SuperJ has a Cargo-inspired build system built into the superj binary. One command scaffolds a project; two more build and run it:

superj new myapp        # scaffold: Build.sj + src/myapp/Main.sj + .gitignore
cd myapp
superj run               # compile + link + run in one step

You should see:

Hello from myapp

The scaffolded project:

myapp/
  Build.sj            ← manifest (name, version, entry, dependencies)
  .gitignore          ← ignores target/
  src/myapp/Main.sj   ← package myapp; class Main { public static void main(String[] args) { ... } }

Edit src/myapp/Main.sj:

package myapp;

public class Main {
    public static void main(String[] args) {
        System.out.println("hello from superj");
    }
}

Then:

superj run               # rebuild + run
superj build --release   # optimized build -> target/release/myapp
superj check             # whole-project type-check (no codegen/link)

For the full build-system surface — Build.sj manifest fields, path and git dependencies, workspaces, profiles, build hooks, feature flags, superj add, superj clean, superj test — see The Build System.

Using the SDK

The SuperJ SDK ships in the package at $SJ_HOME/sdk/. A program that imports sj.* classes (collections, HTTP, JSON, SEDA, crypto, …) compiles against the installed SDK automatically — no flags needed:

superj run               # auto-detects the installed SDK mode

To see the full SDK API surface — every class, method, and signature in one file — read $SJ_HOME/sdk/build/SDK_API.md (generated by superj sdk-api). For a single class:

superj doc sj.http.Router            # print Router's methods + signatures
superj doc sj.util.IntArrayList      # print IntArrayList's surface
superj doc --list                    # print every SDK class (FQN + tag)

A curated set of ~30 examples covering the language surface and all SDK capabilities is at examples/INDEX.md — grep it for a capability (http, json, seda, collections, …), read one example, and you have the pattern.

From source / for contributors

The curl | sh path above is for end users. If you're contributing to SuperJ itself, the contributor guide (AGENTS.md) and dev setup live in the source repository.

IntelliJ plugin (optional)

SuperJ ships an IntelliJ IDEA plugin (syntax highlighting, Go-to-Declaration, run configuration) under plugins/intellij/. The plugin is optional and is not required to use the compiler — it's an IDE convenience only.

The distribution tarball's plugins/intellij/ directory contains the plugin jar when it could be built. Building the plugin needs a JDK 17 and an IntelliJ IDEA installation on the build machine (it links the IDE's jars at compile time). The pack.sh release step tries to build it on demand:

So a headless/CI build box without an IDE produces a fully working SuperJ install, just without the IDE plugin. To build it yourself after installing:

cd plugins/intellij
JAVA_HOME=<jdk-17> mvn -DskipTests -Dij.home <IntelliJ.app/Contents> package

and drop the resulting target/superj-intellij-*.jar into your $SJ_HOME/plugins/intellij/. See the contributor guide in the source repo for IDE install steps.

Where to go next

SuperJ — manual · generated from getting-started.md at pack time