Skip to content
jabrenaPublic

About

JUno is an ahead-of-time compiler for running a practical subset of Java on Arduino boards & M5Stack Cardputer

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

419 Commits

Folders and files

Repository files navigation

JUno: Java for Arduino

JUno /ˈxu.no/ is an ahead-of-time compiler for running a practical subset of Java on Arduino boards & M5Stack Cardputer. It keeps javac as the Java frontend, performs closed-world linking on the development machine, and emits Assembly.

CI Builds

Motivation

Traditionally, Java programs require a JVM to run. However, in memory-constrained environments, running Java programs inside a JVM can be difficult. Juno enables developers to create small Java programs for microcontrollers such as Arduino.

Arduino boards supported

Arduino Uno Family

Arduino UNO Q
Arduino UNO Q
Arduino UNO R4 WiFi
Arduino UNO R4 WiFi
Arduino UNO R3 WiFi Rev2
Arduino UNO WiFi Rev2
Arduino UNO R3
Arduino UNO R3

Arduino Uno shields supported

Shield Image Supported features
LCD Keypad Shield LCD Keypad Shield 16x2 HD44780 character display, five-button keypad, and controllable backlight.
ELEGOO 2.8-inch TFT Touch Shield ELEGOO 2.8-inch TFT Touch Shield 240x320 ILI9341 display, resistive touch input, and an onboard microSD socket.
Arduino 9 Axis Motion Shield Arduino 9 Axis Motion Shield BNO055 heading, pitch, roll, gyroscope, and calibration data over I2C.
PCA9685 PWM & Servo Shield PCA9685 PWM and Servo Shield Sixteen independently controlled 12-bit PWM/servo channels over I2C.
AZDelivery Data Logger Shield AZDelivery Data Logger Shield SPI SD-card access. The shield's real-time clock is not currently exposed.
Arduino WiFi Shield Arduino WiFi Shield Wi-Fi, UDP, TCP, plain HTTP and the SNTP clock on the UNO R3 (shield firmware 1.1.0). No access point or HTTPS.
Grove Base Shield Grove Base Shield Grove digital, analog, I2C, and UART connectors.

Arduino Nano Family

Arduino Nano ESP32
Arduino Nano ESP32

Arduino Nano shields supported

Accessory Image Supported features
Arduino Nano Connector Carrier Arduino Nano Connector Carrier Passive carrier for the Arduino Nano ESP32: microSD card slot on SPI, four Grove sockets, and one Qwiic connector.

M5Stack boards supported

M5Stack Cardputer Adv
M5Stack Cardputer

Quick start

Requirements: Arduino CLI, JDK 27+, Maven 3.10+.

On macOS, install Arduino CLI with Homebrew and verify that it is on your PATH:

brew update
brew install arduino-cli
arduino-cli version

See Arduino's installation guide for direct downloads and other options.

sdk env install
./mvnw clean install

Blink

Blink runs unmodified on every supported board. Its built-in LED alternates on and off every 500 ms (one full cycle per second).

package io.github.jabrena.juno.api.hardware.gpio;

import io.github.jabrena.juno.hardware.ArduinoUno;
import io.github.jabrena.juno.hardware.gpio.DigitalOutput;
import io.github.jabrena.juno.util.Delay;

public final class Blink {

    public static void main(String[] args) {

        DigitalOutput led = DigitalOutput.of(ArduinoUno.getGpio().getBuiltinLed());

        while (true) {
            led.high();
            Delay.millis(500);

            led.low();
            Delay.millis(500);
        }
    }
}

The project provides a Maven plugin to help the user with the common operations. juno:upload generates the sketch, verifies it with arduino-cli, and flashes it in one step. Blink has no board restriction, so pick a target with -Djuno.board:

./mvnw -f juno-examples/pom.xml compile juno:upload \
  -Djuno.main=io.github.jabrena.juno.api.hardware.gpio.Blink \
  -Djuno.board=arduino-uno-q
# -Djuno.board=arduino-uno-r4-wifi
# -Djuno.board=arduino-uno-r3-wifi-rev2
# -Djuno.board=arduino-uno-r3
# -Djuno.board=arduino-nano-esp32

Select another supported board with -Djuno.board. The plugin auto-detects the port when exactly one matching board is connected; otherwise, specify it with -Djuno.port=<PORT>.

After uploading the program, you could monitor what happen in the board using the following command:

./mvnw -f juno-examples/pom.xml juno:monitor \
  -Djuno.board=arduino-uno-q

Maven plugin

The juno Maven plugin provides these goals. The commands below run them from the examples module; add -Djuno.main=<fully-qualified-class-name> and -Djuno.board=<id> when the entry point has no single-board @Board declaration.

Goal Purpose Command
juno:boards Lists the boards and serial ports detected by Arduino CLI without touching a board. ./mvnw -f juno-examples/pom.xml juno:boards
juno:install-deps Installs the Arduino core and optional libraries required by the examples. ./mvnw -f juno-examples/pom.xml juno:install-deps
juno:compile Generates the assembly, C++ runtime shim, and Arduino sketch wrapper. ./mvnw -f juno-examples/pom.xml compile juno:compile -Djuno.main=io.github.jabrena.juno.api.hardware.gpio.Blink -Djuno.board=arduino-uno-r4-wifi
juno:verify Generates the sketch and compiles it with Arduino CLI without using physical hardware. ./mvnw -f juno-examples/pom.xml compile juno:verify -Djuno.main=io.github.jabrena.juno.api.hardware.gpio.Blink -Djuno.board=arduino-uno-r4-wifi
juno:upload Generates, verifies, and uploads the sketch to the selected or detected board. ./mvnw -f juno-examples/pom.xml compile juno:upload -Djuno.main=io.github.jabrena.juno.api.hardware.gpio.Blink -Djuno.board=arduino-uno-r4-wifi
juno:monitor Opens the serial monitor for the selected or detected board. ./mvnw -f juno-examples/pom.xml juno:monitor -Djuno.board=arduino-uno-r4-wifi

For example, verify the Grove Temperature and Humidity Sensor Pro monitor for a UNO WiFi Rev2 without uploading it:

./mvnw -f juno-examples/pom.xml compile juno:verify \
  -Djuno.main=io.github.jabrena.juno.api.hardware.device.grove.sensors.GroveDht22Monitor \
  -Djuno.board=arduino-uno-r3-wifi-rev2

Supported Java subset

Juno is an ahead-of-time compiler, not a JVM, so it intentionally supports a focused, predictable subset of Java that can be translated into native code for resource-constrained Arduino boards. Programs are first compiled normally with javac; Juno then performs closed-world linking from main, follows every reachable method, and validates the bytecode before generating the sketch.

Valid Java is therefore not automatically valid Juno input. If reachable code needs an unsupported bytecode instruction, JDK feature, or invokedynamic bootstrap, compilation stops instead of emitting code with uncertain behavior. The diagnostic identifies the method, bytecode offset, and unsupported operation or bootstrap. The currently supported Java subset includes:

  • Entrypoints and methods: static void main(String[]), the embedded-friendly static void main(), static methods with primitive arguments and results, direct closed-world calls, and removal of unreachable methods. See HelloWorld and Methods.
  • Primitive types: boolean, byte, short, char, int, long, float, and double. The example prints each type's size and range using javac-folded BYTES, MIN_VALUE, and MAX_VALUE constants. See PrimitiveTypes.
  • Values and control flow: constants, mutable static fields, local variables, arithmetic, bitwise and shift operations, comparisons, conditionals, switch, and loops. See Variables, Operators, and ControlFlow.
  • Arrays: fixed-size primitive arrays (boolean through double, including fixed-size multidimensional arrays) allocated from the fixed 8 KiB arena, and static final read-only lookup tables with an all-constant initializer, kept in flash instead of RAM. See Arrays.
  • Strings: string literals, String.valueOf(int), length(), and charAt(int) for runtime string references, equals, plus + concatenation of strings and a StringBuilder. See Strings.
  • BigInteger and BigDecimal: arbitrary-precision java.math.BigInteger and BigDecimal, with MathContext and the RoundingMode constants. Arithmetic, pow, sqrt, division by scale, RoundingMode or MathContext, setScale, round, comparisons, conversions and toString / toPlainString follow the JDK's results, scales and rounding. Values are immutable blocks in the 8 KiB arena and are garbage-collected, so precision is limited by memory, not by a fixed width. See ScopedValuesPrecision.
  • LocalDateTime: now(), MIN, the date and time getters, equals and ISO-8601 toString. now() gets UTC from an SNTP server once Wi-Fi is up (JUNO_SNTP_HOST, default pool.ntp.org; JUNO_SNTP_PORT, default 123) and returns LocalDateTime.MIN while it is unavailable. Works on every Wi-Fi board, including the UNO R3 with the Arduino WiFi Shield. See SntpTime and SntpTimeSD (server read from the SD card).
  • UUID: java.util.UUID supports construction, parsing, formatting, comparison, 16-byte conversion through UuidCodec, and randomUUID() on all boards. On the classic UNO R3, random UUIDs are weak and non-cryptographic; use them only for node identities, as shown by WeakUuid.
  • Objects and interfaces: enums, records, classes, constructors, instance fields, class inheritance, virtual dispatch, and directly implemented interfaces with closed-world dispatch. Inheritance and inherited/default implementations are available on every board, but the AVR boards have much less flash, SRAM and arena space, so the Arduino build or runtime may fail for larger object graphs. See Objects and Interfaces.
  • Lambdas and method references: non-capturing and capturing lambdas, plus static, bound, unbound, and constructor references emitted through LambdaMetafactory.metafactory. See Lambdas.
  • Math and random numbers: the documented java.lang.Math subset and Arduino-backed bounded pseudorandom numbers. See MathFunctionsSinglePrecision, MathFunctionsDoublePrecision and RandomNumbers.
  • Exceptions: throw, try/catch (including multi-catch and catching by a supertype), finally, nested handlers, propagation between methods, custom final exception classes, and try-with-resources. See Exceptions.
  • Concurrency: preemptive virtual threads (Thread.startVirtualThread, join, isAlive) and the supported JDK 27 preview StructuredTaskScope subset with cooperative subtasks (up to four threads including main, with Thread.sleep and Thread.yield), restricted monitors and ReentrantLock, and ScopedValue bindings inherited by forked subtasks. Platform threads (new Thread) and Thread.ofVirtual() builders are rejected at compile time; AtomicInteger, AtomicBoolean and AtomicLong are available. See Synchronization, VirtualThreads, StructuredConcurrency (subtasks owned by a scope that is joined and closed before the owner reads their results), and ScopedValuesPrecision (a scoped MathContext shared by three parallel subtasks).
  • Board services: GPIO, clocks and delays, serial I/O, LED matrices, mouse input, Wi-Fi, HTTP/HTTPS, bounded JSON inspection, and read-only SPI SD-card files through Juno's intrinsic APIs. See Blink (GPIO and delays), SerialCounter (serial I/O), HttpServerStatus (clocks), LedMatrixHeart (LED matrix), WifiStatus (Wi-Fi), WifiStatusSD (Wi-Fi credentials loaded from an SD card), WifiStatusSDLcd (the same, with an LCD Keypad Shield), WifiProvisioning (access point and HTTP server to provide Wi-Fi credentials), HttpsMethods (HTTP/HTTPS and JSON), Miner2 (UDP between boards: factor numbers together and chain the verified answers as a leaderless, Bitcoin-style proof-of-work chain), and SdFileOperations (SD card).
  • Shields: stack a shield on the board as designed, with no rewiring, and use it through Juno's APIs; a combination whose shields share pins, such as the LCD Keypad Shield on a UNO R3 with the Arduino WiFi Shield, is rejected at compile time. The Grove Base Shield brings Grove connectors on the UNO pin map, as shown by GroveDht22Monitor. The LCD Keypad Shield (16x2 display, buttons, backlight) is shown in LcdKeypadDemo. The ELEGOO 2.8" TFT touch shield (display, touch, microSD) is shown in TouchPaintTFT and WifiStatusSDTFT. The AZDelivery Data Logger Shield provides the SD card for WifiStatusSD. The Arduino WiFi Shield brings Wi-Fi, UDP, TCP, plain HTTP and the SNTP clock to the UNO R3, as shown by UdpEcho and PlainHttpWeather.

Maven modules

This is a multi-module Maven build:

  • juno-api/ — the programming model: the Java-facing hardware API the compiler recognizes as intrinsics (api/) and the @Board annotations used to select a compilation target (annotations/). This is the only artifact programs compile against.
  • juno-compiler/ — the compiler (classfile, bytecode, linker, backend, …), which depends on juno-api. Builds the executable juno-compiler/target/juno-compiler-<version>.jar.
  • juno-maven-plugin/ — Maven goals for generating a sketch (juno:compile), compiling it with Arduino CLI (juno:verify), uploading it (juno:upload), and opening the serial monitor (juno:monitor).
  • juno-examples/ — example Java programs written against juno-api, built like any other Maven module so they're checked for compile errors on every build.
  • juno-site/ — the Roq/Quarkus static site generator behind the documentation site. docs/ is entirely generated from Markdown under juno-site/src/main/resources/content/ — see Documentation site below to regenerate it.
  • juno-third-parties/ — integrations maintained outside the core compiler, currently including the independent H8/300 firmware generator for the LEGO RCX, its S-record, and the generated Java representation consumed by juno-examples.

Architecture

Juno is an ahead-of-time compiler, not a virtual machine. An Arduino board has no JVM, so nothing is interpreted at run time: the Java program is compiled with javac as usual, and Juno then translates the resulting JVM bytecode into native ARM assembly that the Arduino toolchain builds into the sketch's firmware. The compiler is closed-world: it only sees the classes reachable from main, so it can resolve every call at build time, drop everything unused, and reject anything outside the supported subset before generating a single instruction. That is also why there is no dynamic class loading, reflection, or general heap.

From Java to a running board

 Hello.java ──javac──▶ Hello.class ──▶ Juno ──▶ Hello.S + HelloShim.cpp + HelloAsm.ino ──arduino-cli──▶ board
                                       │
                 classfile → bytecode → linker → lowering → optimize → backend
  1. Write and compile Java. Programs use the Juno hardware API and optionally select a board with @Board. Maven runs javac; Juno consumes the resulting .class files.
  2. Link and validate. Juno starts at main, keeps only reachable code, converts hardware calls into intrinsics, and rejects unsupported bytecode with a precise diagnostic.
  3. Lower and optimize. Reachable methods become Juno IR, where compiler passes simplify the program and report risks such as excessive arena use, recursion, or unsafe array access.
  4. Generate the sketch. The backend emits native assembly and a small C++ runtime shim. The Maven plugin packages them with a minimal .ino under target/juno/<Main>Asm/.
  5. Build and run. Arduino CLI compiles the generated sketch for the selected board. Use juno:verify to compile it, juno:upload to flash it, and juno:monitor to read its serial output.

References

About

JUno is an ahead-of-time compiler for running a practical subset of Java on Arduino boards & M5Stack Cardputer

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages