Feature Inventory
The full, up-to-date inventory of what Juno supports and what's still missing.
Juno deliberately fails at link time when reachable code uses something outside the current subset. Diagnostics identify the method, bytecode offset, and unsupported operation/bootstrap.
Supported today:
static void main(String[])and the embedded-friendlystatic void main()- static methods with primitive arguments/results, including two-slot
longanddoublevalues - local variables, integer constants, arithmetic, bitwise operations, shifts, comparisons, conditionals, and loops
private/static finalprimitive constants (boolean/byte/char/short/int) initialized with a compile-time constant expression, same class or a different one —javacinlines these as ordinary literals (JLS 4.12.4), so Juno never sees a field read- mutable static primitive fields with JVM default-zero initialization. Reachable
<clinit>methods that use the supported subset execute during generated startup; full JVM initialization ordering and cycle semantics are not yet modeled. - arrays of
boolean/byte/char/short/int/long/float/double:new T[N]with a compile-time-constantN(there is no general heap, so every array is allocated from the fixed program-lifetime arena at its natural width —byte[]/boolean[]asint8_t,char[]asuint16_t,short[]asint16_t,int[]asint32_t,long[]asint64_t,float[]asfloat, anddouble[]asdouble); passing an array to another static method (pointer semantics); returning an array is safe for locally created arrays because array storage comes from Juno's fixed 8 KiB program-lifetime arena.arr.lengthand bounds-checkedarr[i]/arr[i] = vwork only on an array local that is assigned exactly once in its method (i.e. "effectively final") right afternew T[...]— an array received as a parameter, or a reassigned local, still supportsarr[i]/arr[i] = vbut without a bounds check and without.length, since its size isn't known at compile time there. Fixed-size multidimensional primitive arrays are supported when every dimension is a compile-time constant. A bounds check the compiler can prove never fails (a constant index, a counted loop such asfor (int i = 0; i < a.length; i++), a masked index likea[i & 15]into a 16-element array) is decided at compile time and costs nothing at run time; the others compile to a single unsigned compare. - read-only lookup tables in flash: a
static finalone-dimensional primitive array with an all-constant initializer (static final short[] SINE = {0, 804, 1608, ...};) that reachable code only ever reads asTABLE[i]orTABLE.lengthis emitted once into.rodataat its natural element width, instead of being built in the arena by<clinit>. Tables cost flash, not RAM, so they can be far larger than the 8 KiB arena (the UNO R4 WiFi has 256 KiB of flash), and their known length gives every access a bounds check and makes.lengthwork. Flash cannot be written by an ordinary store, so Juno checks each read on the bytecode: a table that is passed to a method, stored in a local or field, written, or selected across a branch ((c ? A : B)[i]) stays in the arena as before, andJUNO-RISK-012says which read kept it there. The index itself may be any expression, including another table read (VALUES[ORDER[i]]).javaclimits one class's static initializer to 64 KiB of bytecode, about 8,000 elements, so split a larger data set across several holder classes. longlocals, method parameters/results, fields, and arrays (see Known Limitations), with arithmetic, shifts, bitwise operations, comparisons, numeric conversions, calls, returns, and loops. JVM locals/stack values are represented internally as paired 32-bit halves and packed to nativeint64_tat storage/call boundaries.floatlocals and static method parameters/results, with constants, arithmetic (including remainder), negation, comparisons,int/floatconversions, calls, returns, and loops. Java's NaN comparison rules and saturating/NaN-to-zero numeric narrowing are preserved. Float fields and arrays are supported.doublelocals, static method parameters/results, fields, arrays, arithmetic, comparisons, numeric conversions, calls, and returns. Two JVM slots are preserved for everydoublelocal and parameter.enumconstants as plain 0-based ordinalints, includingordinal(),values(), equality, method parameters/results, andswitch. An enum may associate one compile-time integer value with each constant through a constructor and expose it through a simple field accessor; Juno emits an immutable ordinal-indexed lookup rather than allocating enum objects.- records as arena objects, including parameters/results, canonical and compact constructors, custom
accessors, and supported primitive/array/reference components. Generated
equals()/hashCode()/toString()remain unsupported because they requireinvokedynamicand broader object/String support. - final closed-world classes with constructors, primitive/reference instance fields, and statically resolvable instance calls. Objects come from a fixed 8 KiB zero-filled arena backed by a conservative mark/sweep garbage collector: a collection runs automatically when an allocation would otherwise exceed the arena, reclaiming any block no longer reachable from reference-valued static fields or any live thread stack. This is Boehm-GC style — no GC type metadata and no compaction, since a collector that can't tell a real pointer from an int that happens to match a heap address can't safely move objects — so a program whose simultaneously live objects exceed 8 KiB still exhausts the arena and panics exactly as before; only the total lifetime allocation count is no longer bounded by that capacity.
- interfaces implemented directly by final closed-world classes, with calls through an interface
reference (
invokeinterface). Reachability is based on concrete classes allocated by reachable code: one reachable implementation becomes an ordinary direct call; two or more use a deterministic object type id and generated switch. Inherited and default interface implementations remain unsupported. - lambdas and method references emitted by
javacthroughLambdaMetafactory.metafactory. Non-capturing lambdas/static method references become immutable function references and do not allocate. Capturing lambdas and bound instance references become arena closure objects with one field per captured scalar/reference. Static, bound instance, unbound instance, and constructor references are supported.java.lang.Runnableand compatible JDK functional interfaces can be used without linking their JDK implementation classes. Lambda captures and parameters currently excludelong/double; method references to Juno hardware/runtime intrinsics andaltMetafactorycall sites remain unsupported. - direct static calls with closed-world reachability; unused methods are omitted
- runtime {@code String} references in locals, parameters, and return values, with string literals,
{@code String.valueOf(int)}, {@code length()}, and {@code charAt(int)}. Juno lowers javac's
{@code StringConcatFactory.makeConcatWithConstants} protocol for {@code String} and primitive operands;
concatenation maps a null string to {@code "null"} and panics if the UTF-8 result exceeds the 31-byte
payload, printing
[juno-string] runtime String longer than 31 characterson Serial before[juno-panic]. Runtime values use eight rotating 32-byte UTF-8 slots instead of the arena, so string-heavy loops remain allocation-free; a value must be consumed before eight newer results overwrite its slot. APIs documented as compile-time-string-only (including networking paths/bodies and LED text unrolling) retain that rule. - opaque
DigitalOutputhandles which compile down to integer pin numbers without heap allocation - UNO R4 WiFi networking through allocation-free
Wifi, plain HTTP, and certificate-validated HTTPSGET,POST,DELETE,PATCH, and RFC 10008QUERYintrinsics; request bodies use compile-time JSON strings - bounded, allocation-free JSON inspection over caller-owned
byte[]buffers: strict whole-document validation, object dot paths and zero-based array indexes, value type/null detection, array sizing, 32/64-bit integers, decimal/exponent numbers, booleans, and JSON-string escape decoding to UTF-8 - read-only SPI SD-card files through the standard
java.io.InputStream, plus intrinsic lowering ofjava.util.Propertiesconstruction,load(InputStream),getProperty, andsize; loaded values have stable arena-backed storage and can be passed directly toWifi.begin - read-only random access to SD-card files through the standard
java.io.RandomAccessFile(mode"r":seek,getFilePointer,length,read,readFully,skipBytes,close), raising the JDK'sFileNotFoundException,EOFExceptionandIOException; - Java-compatible 32-bit wrapping arithmetic and divide-overflow behavior
- Arduino-backed pseudorandom numbers through
Random.seed(int),Random.nextInt(bound), andRandom.nextInt(origin, bound), with exclusive upper bounds java.lang.Math:abs/min/max(int/long/float/double),clamp(Java 21 overloads),floorDiv/floorMod,signum,round,floor,ceil,sqrt,cbrt,pow,hypot,exp,log,log10,sin/cos/tan,asin/acos/atan/atan2, andtoRadians/toDegrees;Math.PIandMath.Eare ordinaryjavacconstants. Each reached overload becomes one runtime-shim helper backed bylibm, with Java's exact semantics where they differ from C (min/maxof-0.0/NaN,round's ties-toward-positive-infinity and saturation,pow's NaN rules,floorDivoverflow). Where Java would throw (floorDiv/floorModby zero,clampwithmin > max), the program panics, like native integer division by zero.java.math: arbitrary-precisionBigInteger,BigDecimalandMathContext, plus theRoundingModeconstants.BigInteger:valueOf,new BigInteger(String),ZERO/ONE/TWO/TEN,add,subtract,multiply,divide,remainder,mod,pow,sqrt,gcd,negate,abs,min,max,shiftLeft/shiftRight,signum,bitLength,compareTo,equals,intValue/longValue/doubleValueandtoString.BigDecimal:valueOf,new BigDecimal(String|int|long|BigInteger[, scale]), the constants,add,subtract,multiply(exact or with aMathContext),divide(exact, with a scale andRoundingMode, with aRoundingMode, or with aMathContext),sqrt,round,setScale,pow,negate,abs,min,max,stripTrailingZeros,movePointLeft/movePointRight,signum,scale,precision,unscaledValue,compareTo,equals,toBigInteger, the numeric conversions,toStringandtoPlainString.MathContext:new MathContext(precision[, mode]),getPrecisionandUNLIMITED/DECIMAL32/DECIMAL64/DECIMAL128. Results, scales, rounding and text follow the JDK exactly (checked against it on randomized operations). Values are immutable blocks in the 8 KB arena and are garbage-collected, so precision is limited by memory, not by a fixed width;toString()returns an arena string, so print it withSerial.println(value.toString())rather than concatenating it (concatenation uses the 32-byte string slots).ArithmeticExceptionandNumberFormatExceptionare raised as in Java. Not supported:new BigDecimal(double), bit operations beyond shifts,modPow/isProbablePrime,divideAndRemainderand the other methods;BigDecimal.valueOf(double)goes through Juno's owndoubleformatting. See the board-readyScopedValuesPrecisionexample.- exception handling:
throw,try/catch(including multi-catch and catching by a supertype),finally, nested handlers, andtry-with-resources, including exceptions that propagate out of the method that throws them to acatch/finallyin any caller (the exception is left pending and each frame returns; callers poll only after calls that can throw, so programs that never throw across a call are unchanged).try-with-resources callsclose()on the normal and the exceptional path. Exceptions are 8-byte arena objects (class id and message) created with the()or(String)constructor ofThrowable,Exception,Error,RuntimeException,IllegalArgumentException,NumberFormatException,IllegalStateException,ArithmeticException,IndexOutOfBoundsException,ArrayIndexOutOfBoundsException,StringIndexOutOfBoundsException,NullPointerException,UnsupportedOperationException,ClassCastException,NegativeArraySizeException,InterruptedException,java.util.NoSuchElementException,java.util.concurrent.TimeoutException, andjava.io.IOException, or of a final program class extending one of them (which may add its own fields).getMessage()is supported. An exception no frame up tomaincatches printsException in thread "main" <class>: <message>overSerialand panics. Anint/longdivision or remainder by zero inside atrywhose handler catchesArithmeticException(or a supertype, orfinally) raisesArithmeticException("/ by zero")there; anywhere else it unwinds to the callers' handlers if anycatch/finallyin the program can receiveArithmeticException, and panics if none can, as do Juno's other runtime failures (array bounds, arena exhaustion). - Cooperative virtual threads and subtasks, on both boards:
Thread.startVirtualThread(Runnable)(a class implementingRunnableor a lambda),join()andisAlive()on the returnedThread, andThread.sleep(long)andThread.yield(), which are also available inside a forked subtask (and inmain). Each virtual thread or subtask runs on a stack of its own, and control moves between them only where the program already pauses — loop iterations (at most once per millisecond),Delay.millis,Thread.sleep,Thread.yieldandjoin— so a statement is never interrupted halfway and the arena, the garbage collector and the pending-exception slot need no locking. A virtual thread is a daemon, as on the JVM: whenmainreturns, the program does not wait for it. At most four threads run at once, the owner included, each extra one with a 2 KiB stack on the UNO R4 WiFi and 4 KiB on the UNO Q (a stack overflow on the UNO R4 panics with[juno-thread] stack overflowwhen the thread next switches away). The collector scans every live stack. The UNO R4 WiFi swaps stack pointers itself; on the UNO Q each thread is a Zephyr thread that waits for a baton, so only one runs at a time. The rest ofjava.lang.Threadis not part of the language subset:new Thread(...),start(),setDaemon(boolean), theThread.ofVirtual()andThread.ofPlatform()builders and the other members fail with aCompileExceptionthat points toThread.startVirtualThread, and so do otherjava.util.concurrentAPIs. - JDK 27 preview
StructuredTaskScope, lowered onto the same cooperative task runtime:open(),open(Joiner),fork(Callable),fork(Runnable),join(),isCancelled(), try-with-resourcesclose(), andSubtask.state()/get()/exception(). The supported built-in joiners areallSuccessfulOrThrow,anySuccessfulOrThrow,awaitAllSuccessfulOrThrow; fail-fast policies cancel sibling tasks and report failures asExecutionException. At most three subtasks can be active because the four scheduler slots include the owner thread. Compile source with JDK 27 and--enable-preview(the examples module does this). Java 21's removedShutdownOnFailure/ShutdownOnSuccessclasses, custom joiners,allUntil, theConfigurationoverload, and the factory overloads that take an exception-mappingFunctionare unsupported.allSuccessfulOrThrow().join()(aListof results in JDK 27) may be ignored or kept as an opaque non-null result; reading the list is rejected at compile time. java.lang.ScopedValue(final in JDK 25, no preview flag):ScopedValue.newInstance(),ScopedValue.where(key, value),Carrier.where(key, value),Carrier.run(Runnable),Carrier.call(CallableOp),get(),isBound()andorElse(value). A bound value is one reference word (aString, array or object; there is no boxing, so bind anint[]holder for numbers). A binding is entered on the calling thread's own stack, so an innerwhere(...)rebinds only for its extent and the outer binding returns afterwards, even if the body throws. Subtasks forked in aStructuredTaskScopeinherit the owner's bindings;get()on an unbound value throwsNoSuchElementException. Not supported:runWhere/callWhere/getWhere,orElseThrow, andCarrier.get. See the board-readyScopedValuesPrecisionexample.java.util.concurrent.atomicsubset:AtomicInteger,AtomicBooleanandAtomicLongwithnew,get,set,getAndSet,compareAndSetand, on the numeric ones,incrementAndGet,decrementAndGet,getAndIncrement,getAndDecrement,addAndGetandgetAndAdd(plusintValue()/longValue()). Each is a small arena cell, and the cooperative scheduler never switches inside an operation, so none needs a monitor.AtomicReference, the array and adder classes,updateAndGet/accumulateAndGetandlazySetare rejected with aCompileException.- restricted synchronization for cooperative subtasks:
volatileprimitive/reference fields are always loaded from and stored to memory, including across calls and loop backedges;synchronized (lock)blocks use a bounded table of eight reentrant intrinsic monitors; and concretejava.util.concurrent.locks.ReentrantLocksupports onlynew ReentrantLock(),lock(), zero-argumenttryLock(), andunlock(), backed by the same monitor runtime. Contendedlock()/monitor entry yields cooperatively until ownership is available. Synchronized methods, fair locks, timed/interruptible locking,Condition, theLockinterface, and the rest ofjava.util.concurrentremain unsupported. See the board-readySynchronizationexample. .classinputs from directories, individual files, or JARs
Runtime-risk inspection
Every compile (standalone CLI or juno-maven-plugin's compile/verify/upload goals)
analyzes the optimized, closed-world IR and prints the findings automatically, before running or
uploading the program. The resulting CompilationReport.runtimeRisks() data is also available
programmatically as immutable records with stable diagnostic codes.
The first analysis slice reports:
| Code | Meaning |
|---|---|
JUNO-RISK-001 |
Arena allocation occurs directly or transitively inside a control-flow loop. |
JUNO-RISK-002 |
The conservative startup allocation estimate exceeds the fixed arena capacity. |
JUNO-RISK-003 |
A recursive call cycle makes generated call depth and stack usage unbounded. |
JUNO-RISK-004 |
Array accesses exist for which Juno cannot emit a bounds check. |
JUNO-RISK-005 |
Integer/long division or remainder (including Math.floorDiv/floorMod) may receive a zero divisor and panic. |
JUNO-RISK-006 |
A dereference uses a value proven null at compile time. |
JUNO-RISK-007 |
A StringBuilder is created with a capacity of 32 or more (or a non-constant one); toString() panics once content reaches the slot size. |
JUNO-RISK-008 |
Thread.startVirtualThread or StructuredTaskScope.fork sits in a control-flow loop; only three threads can be active beside the caller. |
JUNO-RISK-009 |
More than three virtual threads or subtasks are started with no join in between. |
JUNO-RISK-010 |
A thread body (found where a lambda or object is passed to startVirtualThread/fork in the same method) starts a thread or opens a task scope, competing for the same four slots. |
JUNO-RISK-011 |
A thread body reaches a recursive cycle, or its estimated stack exceeds the smallest thread stack (2 KiB on the UNO R4 WiFi). |
JUNO-RISK-012 |
(info) A static final constant-initialized array stays in the arena instead of flash, because a read passes it on, stores it, writes it or selects it across a branch. |
The 8 KiB arena capacity and the count of bounds-checked array accesses (checked at run time, or proved safe at compile time) are exact compiler facts. Arena, static-RAM, and stack figures are conservative source-level estimates: alignment is overestimated, branch feasibility is not modeled, and the downstream C++ compiler may optimize stack locals. The Arduino linker's final memory report is authoritative. Risk findings warn; they do not currently reject compilation.
Two board examples make the distinction observable:
RuntimeRiskSafePulseallocates once, has generated bounds checks, and should report no findings; it blinks alternating 120/360 ms pulses indefinitely.RuntimeRiskArenaExhaustionis intentionally unsafe. It allocates 1 KiB on every loop iteration; itsJUNO-RISK-001warning predicts a pre-collection worst case before upload. With the conservative garbage collector, whether it actually exhausts the arena now depends on reachability: if each iteration's block is dropped before the next (nothing on the stack still points to it), the collector reclaims it and the program keeps running instead of stopping; if every block is kept reachable (e.g. stored into a live array), reclamation is impossible and it still stops — now on whichever iteration first can't fit alongside everything still reachable, rather than always the ninth.
Not yet supported:
- class inheritance/virtual polymorphic dispatch, inherited/default interface implementations, or object arrays with polymorphism
- general string construction, arbitrary-object concatenation, other {@code String} methods, reflection, or dynamic loading
- exception causes, stack traces, suppressed exceptions (
addSuppressedis accepted and dropped, so aclose()failure during unwinding is lost), and exception types in method parameters or return values - enum string methods (
.name(),.toString()),valueOf(), and enum state beyond one directly assigned, compile-time integer value per constant (including mutable fields and per-constant class bodies) - record
equals()/hashCode()/toString()and non-lambda/non-string-concatinvokedynamicbootstrap protocols - the desktop JDK class library
The String[] parameter of a conventional main is accepted as an entrypoint convention, but
there are no command-line arguments on the board and it must not be accessed.