Language Reference (Java → SuperJ)
This is a reference for writers of SuperJ programs, not a tutorial. It assumes you already know Java and covers the deltas: what carries over unchanged, what's removed, what's changed, and what's new. For the ramp-up walkthrough see Getting Started; for the build tool see The Build System.
SuperJ reads like Java and behaves like C. It is not a subset of Java — it keeps the productive parts, drops the parts that fight predictable native performance, and adds systems-level capability Java has no answer for.
1. What's identical to Java
If you can write Java, most of what you write in SuperJ is the same:
- Lexical syntax:
//and/* */comments, identifiers, the operator and punctuator set, semicolons, braces. - Control flow:
if/else,for, enhancedfor(for-each),while,do/while,switch,break,continue,return,throw,try/catch/finally. - Classes & interfaces:
class/interface, singleextends, multipleimplements,abstract,final, access modifiers (public/private/protected/package-private),staticmembers, constructors withthis()/super()chaining,this/super,import/import static, packages. - Generics: class-level and method-level type parameters, bounded (
<T extends Foo>), wildcard? extends/? super/?, diamond<>innew. Erased, just like Java — no reified type tokens. - Enums: with per-constant bodies,
values(),name(),ordinal(). - Exceptions:
try/catch/finally, multi-catch (catch (A | B e)), try-with-resources,throw. (But all unchecked — see §3.) - Method references
::targeting a single-abstract-method (SAM) interface. - Varargs (
T...). @Overrideannotation.- Operators: arithmetic, relational, logical, bitwise, shift, assignment, compound assignment,
?:, string+concatenation (lowered toStringBuilder). - Default values: fields default to
0/0.0/false/nulllike Java; locals have no default (see definite assignment, §3). - Casts and
instanceof, includinginstanceofpattern matching with flow scoping (see §3).
2. What's removed from Java
Each removal buys predictability or removes a class of bug. The replacement, where one exists, is listed.
| Removed | Why | Replacement |
|---|---|---|
Threads, Thread lifecycle, Runnable thread creation | Single-threaded by design — deterministic | Scale with processes + shared mmap + a total-order event stream (SEDA). Runnable/SAM interfaces remain as ordinary types, just not threaded. |
synchronized, volatile, wait/notify/notifyAll, java.util.concurrent | No memory model, no monitors | None. See Concurrency. |
| Lambdas / closures | Hidden capture machinery, unpredictable allocation | :: method references — the sole first-class function syntax. Capture-free. |
| Autoboxing / unboxing | Hides allocation; no wrapper classes | Primitives are never generic type args (List<int> is a compile error). Use primitive-specialized collections (IntArrayList, Int2IntMap, …). |
Reflection, ClassLoader | Everything known at compile time | Static resolution. instanceof/casts still work (vtable + type_id). |
| Non-static inner classes | Hidden enclosing-instance reference | Static nested classes only. Anonymous classes are allowed but do not capture locals/params/enclosing instance. |
| Checked exceptions | Noise; unenforced in practice | All exceptions unchecked. throws allowed but ignored (documentation only). |
finalize() / destroy() / Destroyable | No-op under arenas | Arena reclamation; explicit close()/free()/zeroize() for native resources. See Memory Safety. |
| JPMS modules | Packages only | package a.b.c; ⇔ directory path. |
Labeled break/continue | Complexity | Restructure with a flag, or extract a method. |
switch pattern matching / arrow labels | Complexity | Classic colon form case V: only. |
User-defined annotations (@interface) | No consumer (no reflection, no APT) | Built-in @Override/@Test/@Ignore only. |
3. What's changed from Java
These features still exist but their semantics differ. This is where you trip if you're used to Java.
new is arena-scoped (no GC)
There is no tracing garbage collector. new allocates from an arena:
- By default, the global arena — lives for the whole program, safe to return from a method or store in a field.
- Inside an
arenablock,newallocates from that scoped arena, which is dropped (all memory freed in one O(1) operation) when the block exits.
Scoping is lexical, not dynamic: a new belongs to the arena it's written inside. A method you call from inside an arena block still uses the global arena unless it opens its own block.
Node createNode(int v) {
return new Node(v); // global arena — safe to return
}
void process() {
arena temp {
Buffer buf = new Buffer(1024); // in temp
Node n = createNode(5); // in global (createNode has no arena block)
// outer = buf; // compile error: buf would escape its arena
} // temp dropped: buf freed, n still valid
}
A reference that would outlive its arena is a compile error, not a use-after-free. There is no destroy()/finalize() — arena drop is the reclamation mechanism. See Memory Safety.
local — stack allocation (developer privilege)
local declares a stack-allocated object or array (lowered to alloca):
local Point p = new Point(1, 2); // on the stack, freed on function return
Constraints: not with primitives (local int x is an error), must have an initializer. The compiler does NOT escape-check local — a local ref that escapes its scope is undefined behavior, like a C stack pointer. Use arena blocks for the checked, safe form.
string / String is UTF-8, byte-indexed
One type (string is a keyword alias for String). Deliberately different from Java:
Java String | SuperJ String | |
|---|---|---|
| Encoding | UTF-16 | UTF-8 by construction |
length() | UTF-16 code units | byte count of the UTF-8 encoding |
charAt(i) | a 16-bit char | codepoint index; use charAt/codepoint access returning int |
| Backing | char[] | byte[]-backed, length-prefixed, NUL-terminated for zero-copy C handoff |
== | reference identity | reference identity (same as Java — use .equals() for content) |
An embedded NUL is an ordinary byte — "ab\0cd".length() == 5, and all String methods honor the stored length, not the first NUL. The one place NUL bites: the raw C-string view handed to printf("%s")/native code stops at the first NUL. True binary belongs in a byte[], not a String.
Arrays carry a runtime elem_size and are not Object
Layout: { i32 length, i32 elem_size, i8 data[] } — an 8-byte header then elements. Consequences:
- Arrays have no vtable, no
type_id— they're notObject, not assignable toObject, not validinstanceof/cast targets. System.arraycopyis the structural copy intrinsic; it reads this header.- Every access is bounds-checked at runtime (
ArrayIndexOutOfBoundsException). The--no-bounds-checkbuild flag disables it for hot numeric loops.
Generics are erased, and primitives aren't allowed as type args
T erases to Object; T extends Foo erases to Foo. Bridge methods are generated where erased signatures differ. There is no autoboxing, so List<int> is a compile error — use a primitive-specialized collection (IntArrayList, Int2IntMap, …) or int[].
Exceptions are all unchecked
try/catch/finally/throw/multi-catch/try-with-resources all work as in Java. throws on a declaration is accepted but not enforced — it's documentation. Every exception is unchecked; the compiler never makes you catch or declare one.
instanceof pattern matching with flow scoping
if (obj instanceof String s) {
// s is in scope and typed String here, and only here
return s.length();
}
// s is NOT in scope here
Flow scoping: the binding is in scope in the if then-branch, the while loop body, and after a guarding if (!(obj instanceof T t)) return;. The binding is final — reassigning it is a compile error. Arrays are excluded as instanceof/cast targets (no vtable).
Definite assignment is enforced
A local read before it's assigned is a compile error, same as Java — but SuperJ enforces it strictly. No accidental garbage reads.
4. What's new in SuperJ (not in Java)
arena blocks — scoped memory
arena temp {
// new allocates from temp
} // temp dropped here — O(1), no scan, no pause
Lexical, not dynamic. The compile-time escape check makes a dangling reference a compile error. See §3 above and Memory Safety.
local — stack-scoped allocation
See §3. Developer-privilege, UB-on-escape — use arena for the checked form.
Wide integers: i128, i256, i512
First-class signed primitives, usable everywhere int/long are:
i128 mask = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL;
i256 big = mask * mask;
i512 huge = ...;
long to = (long) mask; // narrowing cast
- All signed; no unsigned variant.
- Literals: decimal/hex/octal/binary, suffix
L/i128/i256/i512,_digit separators. i512cannot appear in anativemethod signature (no C ABI descriptor) and is not astructfield type;i128/i256can.Math.*Exactgives checked arithmetic for the narrow widths; wide-int arithmetic wraps on overflow (two's complement).
struct — fixed-layout wire types
A struct is a reference type with a known byte layout — no vtable, no inheritance, no interfaces. Designed for wire formats and zero-copy protocol code. Full spec: doc/struct.md (shipped with the SDK sources).
struct Header {
int magic;
long timestamp;
byte[16] uuid; // fixed-length array field
string payload; // var-length: UTF-8 text in the trailing data area
}
- Packed, little-endian, declaration order = byte order (no padding).
- Two construction modes:
new Header()(arena allocation, zero-init) and the flyweightHeader.view(buf, off)— binds a reference to existing bytes, no copy, no allocation.bindTo(buf, off)re-points it. - Var-length
bytes/stringfields: an 8-byte slot in the fixed area (4-byte offset + 4-byte length) points into a trailing data area. Writes are strict declaration order, at most once each;reset()starts a fresh build. sizeofisHeader.SIZE(astatic final int).- No class references as fields — a pointer in wire bytes is meaningless across processes.
string/bytesmean the var-length text/blob, not a pointer.
Rectangular arrays: int[,], int[,,]
Contiguous, stride-indexed, a distinct type from jagged int[][]:
int[,] m = new int[3, 4]; // single allocation, row-major
m[1, 2] = 7;
int total = m.length; // 12 (product of dimensions)
- Number of commas + 1 = rank (
int[,]rank 2,int[,,]rank 3). - Every dimension sized at creation — no jagged/empty dims.
.lengthis the total element count; per-dimension sizes are not exposed.- Not assignable to
int[][],int[], orObject.
First-class SIMD vectors
Accessed via the builtin Simd class — no new syntax, no new keywords. Small fixed-size arrays are the container; Simd methods operate on them as vector registers:
float[4] a = ..., b = ..., c = new float[4];
Simd.addFloat4(a, 0, b, 0, c, 0); // c = a + b, 4 lanes at once
Vector families (element type + lane count, encoded in the method-name suffix):
| Width | ISA | Families |
|---|---|---|
| 128-bit | SSE2 | Byte16, Short8, Int4, Long2, Float4, Double2 |
| 256-bit | AVX2 | Byte32, Short16, Int8, Long4, Float8, Double4 |
i128 and boolean are not SIMD element types. Naming: Simd.operation<Family>(src, srcOff, …, dst, dstOff). Operations: arithmetic, bitwise, compare (produce mask lanes 0/-1), blend, convert (widen/narrow), reduce (sum/min/max/any/all), math (sqrt/floor/fma), gather/scatter, dot, prefix-sum, load/store (aligned + unaligned). Full list: SIMD.md (shipped with the SDK sources). --simd=sse2|avx2|none selects the target ISA (sse2 default; avx2 enables the 256-bit families).
native methods — straight to the C ABI
public class Crypto {
public native static long hash(byte[] data, int len);
}
- Declaration ends with
;(no body); the compiler emits an externaldeclare, the linker resolves the symbol. - Symbol mangling:
sj_<package>_<Class>_<method>__<argDescriptor>(JNI-style;i128→Q,i256→O;i512not allowed). Instance natives receivethisas an implicit first C param. Stringmarshals asconst char*(NUL-terminated UTF-8) in andchar*out; other references arevoid*. Primitives map to the obvious C types.- Natives allocate through the global arena (
sj_galloc) — usingmallocand returning that is UB. - Raise SuperJ exceptions via
sj_throw*helpers (they don't return).
The systems SDK (at a glance)
Batteries included; every package is under sj.*:
| Package | What's in it |
|---|---|
sj.lang | String, StringBuilder, Math, Integer, Long, Short, Byte |
sj.util | ArrayList, IntArrayList, LongArrayList, HashMap, Int2IntMap, Int2ObjMap, HashSet, Arrays, Hash, Random |
sj.io | File, FileReader, FileWriter, Closeable |
sj.http | incremental HTTP/1.1 server, ContentType, Cookie, TLS 1.3 |
sj.json | hand-written incremental JSON parser |
sj.seda | the event-driven framework (Apps → Sequencer → event queue) |
sj.crypto | SHA256 (ARM NEON), Secp256k1, EthereumAddress, EIP-712, TLS engine |
sj.net | NioAdapter event loop (TCP/UDP/Unix, epoll/kqueue), UnixGateway |
sj.test | Asserts, the unit-test harness |
Discover the API with superj doc --list and superj doc <class-fqn>.
5. Quick syntax table
Scan for the construct you need. "Java form" is what you'd write in Java; "SuperJ form" is what you write here.
| Construct | Java form | SuperJ form | Note | |
|---|---|---|---|---|
| Hello world | public class H { public static void main(String[] a){ System.out.println("hi"); } } | same | identical | |
| String type | String | string or String | string is a keyword alias; one type | |
| String length | s.length() (UTF-16 units) | s.length() (bytes) / s.charCount() (codepoints) | UTF-8 | |
| Declare a generic list | List<Integer> xs | ArrayList<Integer> — or IntArrayList | no int as type arg; no boxing | |
| Lambda | () -> doX() | Thing::doX | method refs only; target a SAM interface | |
| Functional target | Runnable r = () -> … | Runnable r = MyClass::run | SAM interface, capture-free | |
| Inner class | class Outer { class Inner {} } | class Outer { static class Inner {} } | static nested only | |
| Anonymous class w/ capture | new Foo() { void m(){ use(local); } } | new Foo() { void m(){ /* can't use local */ } } | anonymous classes don't capture | |
| Thread | new Thread(r).start() | run a process; or a SEDA app | no threads | |
| Lock | synchronized (obj) { … } | — | none; single-threaded | |
| Memory cleanup | obj = null; (hint to GC) | nothing; or arena block drop | no GC; arena frees on scope exit | |
| Scoped temp memory | (no equivalent) | arena temp { … } | new — O(1) drop at block exit | |
| Stack object | (no equivalent) | local Point p = new Point(1,2); | new — UB if it escapes | |
| 128-bit int | BigInteger | i128 x = …; | first-class primitive | |
| 256-bit int | BigInteger | i256 x = …; | first-class primitive | |
| 512-bit int | BigInteger | i512 x = …; | new; can't cross native | |
| Wire-format record | (no equivalent) | struct Header { int magic; … } | new; Header.view(buf, off) flyweight | |
| 2-D dense array | int[][] grid | int[,] grid | new; contiguous, stride-indexed | |
| 3-D dense array | int[][][] cube | int[,,] cube | new | |
| SIMD add 4 floats | (no direct equiv) | Simd.addFloat4(a, 0, b, 0, c, 0) | new; float[4] is the container | |
| Call into C | JNI boilerplate | public native static long hash(byte[] d, int n); | new; mangled sj_… symbol | |
| Checked exception | throws IOException | throws IOException (accepted, not enforced) | all unchecked | |
| Try-with-resources | try (var r = …) { … } | same | resource needs a void close() | |
| Multi-catch | `catch (A | B e)` | same | |
instanceof pattern | if (x instanceof T t) | same | flow scoping; arrays excluded | |
| Test method | JUnit @Test | @Test static void name() { … } + // UNIT marker | built-in harness | |
| Skip a test | @Ignore (JUnit) | @Test @Ignore static void name() { … } | built-in | |
| File extension | .java | .sj | ||
| Build | javac / mvn / gradle | superj build / superj run | see Build System | |
| Run | java -cp … Main | ./target/debug/<name> | native binary |
6. Reserved keywords
abstract boolean break byte case catch char
class const continue default do double else
enum extends final finally float for goto
if implements import i128 i256 i512 int
interface instanceof local long native new null
package private protected public return short static
string struct super switch synchronized this throw
throws try transient void volatile while assert
strictfp
New vs Java: arena (scoped memory), local (stack allocation), i128/i256/i512 (wide ints), string (alias for String), struct (wire-format type).
Reserved-but-unused (inherited from Java's keyword set, not implemented): const, goto, assert, strictfp, transient, synchronized, volatile. They're reserved so you can't use them as identifiers; the features they name in Java are removed (§2) or not implemented.