Build desktop apps with Java + HTML/CSS/JS — powered by native webviews.
A JavaFX-inspired application framework backed by the OS's native WebView
(WebView2 on Windows, WebKit on macOS, WebKitGTK on Linux).
Panama FFI. Virtual threads. Zero bundled Chromium.
WARNING: WORK IN PROGRESS
Jux Toolkit is a personal invention born from my own ideas and experimentation. It is currently in active development and is NOT production-ready. You will encounter bugs, missing features, and rough edges -- that's expected at this stage. The API is actively evolving: signatures, behavior, and module structure may change between versions as issues are discovered and fixed, and as the developer experience is refined to make things simpler and more intuitive to use. If something feels wrong or broken, it probably is, and it's likely already on the roadmap to improve. Use at your own risk, and expect breaking changes as the project matures.
Platform support: Jux is designed to be cross-platform (Windows, macOS, Linux). Development and testing are currently focused on Windows first. macOS and Linux support are coming soon.
Dark theme — built entirely with HTML, CSS, and JavaScript rendered in a native webview.
Light theme — same application, zero code changes. Just CSS.
Multi-window — independent secondary windows, each with its own webview, IPC, and event loop.
Robot — desktop automation with live magnifier, screen capture, pixel color, mouse/keyboard control. All via Rust FFI, no AWT/Swing.
Forget the limitations of traditional Java UI toolkits. With Jux, your desktop application's interface is a real browser engine — the same one that powers billions of web pages. That means you get instant access to the entire web frontend ecosystem:
- Tailwind CSS — utility-first styling, dark mode, responsive layouts, all the classes you already know
- Bootstrap — drop in a CDN link and you have a complete component library with grids, modals, tooltips, and carousels
- React, Vue, Svelte, Angular — build your UI with any modern framework, then serve it through Jux
- Three.js / D3.js / Chart.js — 3D graphics, data visualizations, and interactive charts — right inside your desktop app
- Lottie / GSAP / Framer Motion — buttery-smooth animations that would take months to implement in Swing or JavaFX
- Google Fonts / Font Awesome / Lucide Icons — thousands of fonts and icon sets, one
<link>tag away - Monaco Editor / CodeMirror — embed a full VS Code-grade code editor in your app in minutes
- Markdown renderers, PDF viewers, syntax highlighters, rich text editors — if it works in a browser, it works in Jux
This is the fundamental advantage: you are not learning a new UI framework. You are using the web — the most mature, most documented, most ecosystem-rich UI platform ever created — and driving it from Java with full type safety, observable properties, and virtual threads.
Your designers can use Figma and export directly to HTML/CSS. Your frontend devs can use their existing skills. Your Java backend logic stays in Java where it belongs. No compromises.
"Any UI you can build in a browser, you can ship as a native desktop app with Jux."
The native layer of Jux Toolkit is entirely built on top of the amazing Tauri ecosystem — specifically the wry (webview) and tao (windowing) crates. None of this would be possible without the incredible work of the Tauri developers — they are some of the best engineers in the open-source community. Their cross-platform webview abstraction and robust event loop are the backbone that powers every Jux window. A massive thank you to the entire Tauri team for making projects like this possible.
Jux Toolkit lets you write desktop applications in Java with a web-based UI. Your business logic, IPC handlers, and window management live in Java. Your interface is HTML + CSS + JavaScript rendered by the platform's native webview. Communication flows over a typed JSON IPC channel with Java handlers running on virtual threads.
Think of it as JavaFX meets Tauri — the developer experience of extend Application, override start(Window), call launch(), but rendering with a real browser engine instead of JavaFX's Prism pipeline.
| Jux Toolkit | Electron | JavaFX | |
|---|---|---|---|
| Binary size | ~2 MB (system webview) | ~150 MB (bundled Chromium) | ~40 MB (JFX runtime) |
| Rendering | Native WebView2/WebKit | Chromium | Prism/OpenGL |
| DevTools | Full browser DevTools | Full browser DevTools | Scenic View (limited) |
| Language | Java + HTML/CSS/JS | JS/TS + HTML/CSS | Java + FXML/CSS |
| Threading | Virtual threads (JDK 25) | Node.js event loop | Platform thread |
| Memory | Low (no bundled engine) | High | Medium |
public class HelloApp extends Application<Window> {
@Override
public void start(Window window) throws Exception {
window.setTitle("Hello Jux");
window.setSize(800, 600);
window.center();
// Register a command callable from JavaScript
window.command("greet", msg -> {
String name = msg.get("name", String.class);
return IpcResponse.ok("Hello, " + name + "!");
});
// Load your web UI
window.loadHtml("""
<!DOCTYPE html>
<html>
<body>
<h1>Hello from Jux!</h1>
<button onclick="greet()">Click me</button>
<p id="result"></p>
<script>
async function greet() {
const res = await __jux.invoke('greet', { name: 'World' });
document.getElementById('result').textContent = res;
}
</script>
</body>
</html>
""");
window.show();
}
public static void main(String[] args) {
launch(HelloApp.class, args);
}
} Your Java Application
|
+-------------+-------------+
| | |
jux-ui jux-dom jux-net jux-robot
(Window, (Document, (Request/ (Mouse,
Application, Element, Response Keyboard,
IPC) Events) Interception) Screen Capture)
| | | |
+------+------+------+------+------+------+
| |
jux-core jux-base
(Platform, (Properties,
Native Load) Collections,
Bindings)
|
Panama FFI (java.lang.foreign)
|
C ABI (Rust cdylib)
|
wry + tao + enigo + xcap + WebView2/WebKit
|
OS APIs / Webview
Pure Java, zero dependencies. Provides a full JavaFX-style observable properties and collections system.
Properties — typed, observable, bindable:
SimpleStringProperty name = new SimpleStringProperty("World");
name.addListener((obs, oldVal, newVal) -> {
System.out.println("Changed: " + oldVal + " -> " + newVal);
});
name.setValue("Jux"); // fires listener
// Binding
SimpleStringProperty greeting = new SimpleStringProperty();
greeting.bind(Bindings.createStringBinding(
() -> "Hello, " + name.getValue() + "!",
name
));
// greeting.getValue() == "Hello, Jux!"Observable Collections — fine-grained change tracking:
ObservableList<String> items = JuxCollections.observableArrayList();
items.addListener((ListChangeListener.Change<? extends String> c) -> {
while (c.next()) {
if (c.wasAdded()) System.out.println("Added: " + c.getAddedSubList());
if (c.wasRemoved()) System.out.println("Removed: " + c.getRemoved());
}
});
items.addAll("Alpha", "Beta"); // prints: Added: [Alpha, Beta]Available types:
StringProperty, IntegerProperty, LongProperty, DoubleProperty, FloatProperty, BooleanProperty, ObjectProperty<T> — each with Simple*, ReadOnly*, and ReadOnly*Wrapper variants.
Collections: ObservableList<E>, ObservableMap<K,V>, ObservableSet<E>, with JuxCollections factory and Bindings utility class.
Detects OS and architecture, extracts the native library + launcher from the JAR to ~/.jux-toolkit/lib/, and loads it via System.load().
Platform.getOS(); // WINDOWS, MACOS, LINUX, UNKNOWN
Platform.getArch(); // X86_64, AARCH64, UNKNOWN
Platform.isWindows(); // true/false
Platform.isMacOS(); // true/false
Platform.isLinux(); // true/falseAlso provides the shared exception hierarchy (JuxException, JuxNativeException, JuxIpcException, etc.) and the @Component annotation for DOM components.
UI thread management lives in jux-ui's Platform class:
Platform.runLater(() -> window.setTitle("Updated"));
Platform.runAndWait(() -> window.setSize(800, 600));
Platform.isJuxThread(); // true if on the event loop thread
Platform.isBackgroundThread(); // true if NOT on UI thread
Platform.checkJuxThread(); // throws if not on UI thread
Platform.isInitialized(); // check toolkit state
Platform.isImplicitExit(); // check implicit exit setting
Platform.setImplicitExit(false); // keep running for tray apps
Platform.exit(); // request application exitManipulate the browser DOM from Java with a rich, typed API. Selector-based queries, typed element interfaces, a JavaFX-style event system, and a powerful HTML builder pattern.
Document & Element:
Document doc = window.getDocument();
// Query and manipulate
doc.getElementById("title").setInnerText("Hello!");
doc.select(".card").addClass("active");
doc.querySelector("input").setAttribute("placeholder", "Search...");
// Typed element access
Input input = doc.getElementById("email").as(Input.class);
input.setValue("user@example.com");
input.setPlaceholder("Enter email");
// DOM manipulation
doc.getElementById("list").appendHtml("<li>New item</li>");
doc.querySelector(".old").remove();HTML Builder — Build UI Trees Declaratively:
// Create detached elements in memory, inject once
Element card = doc.create("div").className("card", "shadow").children(
doc.create("h2").text("User Profile"),
doc.create("p").text("Welcome back!"),
doc.create("button")
.className("btn", "btn-primary")
.text("Edit Profile")
.onClick(e -> editProfile())
);
doc.byId("content").setInnerHtml(card);
// Build forms with events, accessibility, and validation
Element form = doc.create("form").className("login-form").children(
doc.create("input").id("email")
.attr("type", "email").attr("placeholder", "Email")
.boolAttr("required").onInput(e -> validate()),
doc.create("button").attr("type", "submit")
.text("Sign In").onClick(e -> submit())
);
// Build tables from data
Element table = doc.create("table").children(
doc.create("thead").children(
doc.create("tr").children(
doc.create("th").text("Name"),
doc.create("th").text("Email")
)
),
doc.create("tbody").children(
users.stream().map(u -> doc.create("tr").children(
doc.create("td").text(u.name()),
doc.create("td").text(u.email())
)).toArray(Element[]::new)
)
);Convenience Builder Methods:
// Concise shorthand for common operations
doc.create("div")
.id("progress")
.className("bar", "animated")
.style("width", "75%")
.role("progressbar")
.aria("valuenow", "75")
.data("status", "loading")
.title("75% complete")
.tabIndex(0)
.draggable(true)
.contentEditable(false);Events — Three ways to register:
// 1. Type-safe EventType constants (full control, returns EventRegistration)
EventRegistration reg = doc.byId("btn").addEventHandler(MouseEvent.CLICK, e -> save());
reg.remove(); // can remove later
// 2. String-based (jQuery-style, concise)
element.on("click", e -> save());
// 3. Typed convenience methods (most concise, fully typed)
element.onClick(e -> save());
element.onKeyDown(e -> { if ("Enter".equals(e.getKey())) submit(); });
element.onInput(e -> updatePreview(e.getValue()));
element.onMouseEnter(e -> showTooltip());
element.onScroll(e -> handleScroll(e.getElementScrollTop()));
element.onDragStart(e -> beginDrag(e));Event type hierarchy:
Event.ROOT
+-- DomEvent.ANY
| +-- MouseEvent.ANY (CLICK, DBLCLICK, MOUSEDOWN, MOUSEUP, MOUSEMOVE, ...)
| +-- KeyEvent.ANY (KEYDOWN, KEYUP, KEYPRESS)
| +-- InputEvent.ANY (INPUT, CHANGE)
| +-- FocusEvent.ANY (FOCUS, BLUR, FOCUSIN, FOCUSOUT)
| +-- ScrollEvent.ANY (SCROLL)
| +-- DragEvent.ANY (DRAGSTART, DRAG, DRAGEND, DRAGOVER, DROP)
+-- WindowEvent.ANY (SHOWN, HIDDEN, CLOSE_REQUEST, FOCUSED, RESIZED, MOVED, ...)
Typed elements: Input, Image, Form, Anchor, Select, Video, Canvas — each with element-specific methods.
Web APIs from Java: LocalStorage (getItem/setItem/removeItem), Console (log/warn/error/table/time), DomTransaction (batched writes).
Component system:
@Component(path = "components/sidebar.html")
public class Sidebar extends JuxComponent {
@Override
protected void onMount(Element root, Document document) {
document.byId("nav-home").onClick(e -> navigate("home"));
}
}
document.setContent("sidebar", new Sidebar());Intercept, modify, or block HTTP requests and responses made by the webview.
// Log all requests
window.getNetworkInterceptorChain().addRequestInterceptor("logger", request -> {
System.out.println(request.getMethod() + " " + request.getUri());
return RequestDecision.proceed(request);
});
// Proxy API calls
window.getNetworkInterceptorChain().addRequestInterceptor("proxy", request -> {
if (request.getUri().getHost().equals("api.example.com")) {
request.setUri(URI.create("http://localhost:8080" + request.getUri().getPath()));
}
request.setHeader("Authorization", "Bearer " + getToken());
return RequestDecision.proceed(request);
});
// Block ads
window.getNetworkInterceptorChain().addRequestInterceptor("adblock", request -> {
if (request.getUri().getHost().contains("ads.")) {
return RequestDecision.block();
}
return RequestDecision.proceed(request);
});
// Modify responses
window.getNetworkInterceptorChain().addResponseInterceptor(response -> {
response.setHeader("Access-Control-Allow-Origin", "*");
return response;
});Programmatic mouse, keyboard, and screen control — all via Rust FFI. No java.awt.Robot, no AWT/Swing dependency.
Robot robot = Robot.create();
// Mouse
robot.mouseMove(500, 300);
robot.mouseClick(MouseButton.LEFT);
robot.mouseMoveSmoothly(800, 600, Duration.ofMillis(500));
robot.mouseScroll(0, -3);
robot.mouseDrag(100, 100, 400, 400, MouseButton.LEFT);
MousePosition pos = robot.mousePosition(); // global desktop coordinates
// Keyboard
robot.typeText("Hello, World!");
robot.keyPress(KeyCode.CONTROL_LEFT);
robot.keyType(KeyCode.C);
robot.keyRelease(KeyCode.CONTROL_LEFT);
// Screen capture
ScreenCapture capture = robot.captureScreen(); // full primary monitor
ScreenCapture region = robot.captureRegion(x, y, w, h); // any region, any monitor
Color pixel = robot.pixelColor(500, 300); // works across all monitors
capture.saveAsPng(Path.of("screenshot.png"));
byte[] pngBytes = capture.toPngBytes(); // in-memory PNG encoding
// Monitor enumeration
List<ScreenInfo> monitors = robot.screens();
// Live magnifier — continuously captures area around cursor
MouseCaptureBox box = MouseCaptureBox.builder()
.captureSize(80, 80)
.frameRate(20)
.build();
box.start(robot, (frame, error) -> {
String png = frame.toBase64Png(); // high-quality PNG for display
Color center = frame.centerColor(); // pixel under cursor
});
box.stop();The top-level module. Contains Application, Window, Dialog, and all native FFI access. This is the only module that calls native code directly. Window and Dialog both extend an internal BaseWindow class that provides core infrastructure (handles, properties, webview, IPC, events).
Window — fully property-driven:
public void start(Window window) {
// All state is observable
window.setTitle("My App");
window.setSize(1024, 768);
window.center();
window.setResizable(true);
window.setAlwaysOnTop(false);
// React to state changes
window.titleProperty().addListener((obs, old, title) ->
System.out.println("Title: " + title));
window.widthProperty().addListener((obs, old, w) ->
System.out.println("Width: " + w));
window.focusedProperty().addListener((obs, old, focused) ->
System.out.println("Focused: " + focused));
// Bind properties
SimpleStringProperty appTitle = new SimpleStringProperty("My App");
window.titleProperty().bind(appTitle);
appTitle.setValue("Updated"); // window title updates automatically
// Window events
window.addEventHandler(WindowEvent.CLOSE_REQUEST, event -> {
event.consume(); // prevent close
showConfirmDialog();
});
// DevTools
window.setDevToolsEnabled(true);
window.openDevTools();
window.show();
}IPC — Java commands callable from JavaScript:
// Java side — manual extraction
window.command("save-user", msg -> {
String name = msg.get("name", String.class);
int age = msg.get("age", Integer.class);
db.save(new User(name, age));
return IpcResponse.ok(Map.of("saved", true));
});
// Java side — POJO databinding (Jackson Databind, auto-loaded)
window.command("save-user", msg -> {
User user = msg.as(User.class); // JSON -> POJO
db.save(user);
return IpcResponse.ok(user); // POJO -> JSON
});
// Typed messaging — receive POJOs directly
window.on("user:created", User.class, (User user) -> {
System.out.println("New user: " + user.getName());
});
// JavaScript side
const result = await __jux.invoke('save-user', { name: 'Alice', age: 30 });
console.log(result.saved); // truePush events from Java to JavaScript:
// Java: push data to the browser
window.emit("stats-update", """
{"cpu": 42, "memory": 68}
""");
// JavaScript: listen for events
__jux.listen('stats-update', (data) => {
document.getElementById('cpu').textContent = data.cpu + '%';
});Custom chrome (frameless window with snap layout support):
window.setCustomChrome(true);
window.setTitleBarHeight(40);
window.setDragControl("titlebar"); // draggable caption area
window.setMinimizeControl("btn-min");
window.setMaximizeControl("btn-max"); // Windows 11 snap layouts work automatically
window.setCloseControl("btn-close");Dialogs:
// Custom dialog with typed result
Dialog<String> dialog = new Dialog<>(window);
dialog.setTitle("Enter Name");
dialog.setSize(400, 300);
dialog.loadResources("/web/dialogs", "name-input.html");
dialog.command("submit", msg -> {
dialog.setResult(msg.get("name", String.class));
dialog.close();
return IpcResponse.ok();
});
Optional<String> name = dialog.showAndWait();
// Native file dialogs
Optional<Path> file = new FileDialogBuilder()
.title("Open Project")
.addFilter("Java Files", "java", "jar")
.openFile();
// Native message dialogs
MessageDialog.info("Success", "File saved.");
boolean ok = MessageDialog.confirm("Delete?", "This cannot be undone.");System tray, menus, plugins — all supported. See the architecture guide for complete API docs.
Multi-window — independent windows at runtime:
Each secondary window has its own webview, IPC channel, event loop, and OS thread:
// Create a secondary window on a virtual thread
Thread.ofVirtual().name("settings-window").start(() -> {
Window settings = new Window();
settings.setOwner(primaryWindow); // z-ordering, auto-close with owner
settings.setTitle("Settings");
settings.setSize(500, 400);
settings.initWebView(true);
settings.loadHtml(settingsHtml);
// Per-window IPC — isolated from other windows
settings.command("save", msg -> {
saveSettings(msg.as(Settings.class));
return IpcResponse.ok();
});
settings.show(); // blocks until window closes
});
// Track all open windows
Window.getWindows().addListener((ListChangeListener<Window>) change ->
System.out.println("Open windows: " + Window.getWindows().size()));
// Typed dialogs with virtual-thread-friendly showAndWait()
Dialog<String> dialog = new Dialog<>(primaryWindow);
dialog.setTitle("Enter Name");
dialog.setSize(400, 250);
dialog.initWebView(true);
dialog.loadHtml(formHtml);
dialog.command("submit", msg -> {
dialog.setResult(msg.getString("name"));
dialog.dismiss();
return IpcResponse.ok();
});
Optional<String> name = dialog.showAndWait(); // blocks virtual thread, not UIdemo (examples)
|
+-- jux-ui (api) <-- top-level user-facing module
+-- jux-dom (api)
+-- jux-net (api)
jux-ui <-- main module with native FFI access
+-- jux-dom (api)
+-- jux-net (api)
+-- jux-core (api)
+-- jux-base (transitive via jux-core)
jux-robot <-- standalone desktop automation, native FFI
+-- jux-core (api)
+-- jux-base (transitive via jux-core)
jux-net
+-- jux-core (api)
jux-dom
+-- jux-core (api)
jux-core
+-- jux-base (api)
jux-base <-- foundation, zero dependencies
Jux uses a Rust C ABI crate (c-abi/) compiled as a cdylib. The Java side calls it via Panama FFI (java.lang.foreign). The Rust crate wraps:
| Crate | Version | Purpose |
|---|---|---|
| wry | 0.49 | WebView abstraction (WebView2/WebKit/WebKitGTK) |
| tao | 0.33 | Cross-platform windowing & event loop |
| muda | 0.17 | Application & context menus |
| tray-icon | 0.21 | System tray icons |
| rfd | 0.15 | Native file dialogs |
| enigo | 0.3 | Cross-platform mouse/keyboard simulation (jux-robot) |
| xcap | 0.0.14 | Cross-platform screen capture & monitor enumeration (jux-robot) |
| cbindgen | 0.27 | Auto-generates jux.h from Rust |
The generated C header (c-abi/include/jux.h) is consumed by jextract to produce the Panama bindings in com.xss.it.jux.ui.ffi.JuxLib.
jux-toolkit/
+-- jux-base/ Observable properties, collections, bindings
+-- jux-core/ Platform detection, native lib loading, shared types
+-- jux-dom/ DOM API, Element hierarchy, events, components
+-- jux-net/ Network request/response interception
+-- jux-robot/ Desktop automation (mouse, keyboard, screen capture)
+-- jux-ui/ Application, Window, Dialog, IPC, native FFI bridge
+-- c-abi/ Rust C ABI crate (wry + tao)
| +-- src/ Rust source (app.rs, window.rs, webview.rs, ...)
| +-- include/jux.h Generated C header (cbindgen)
| +-- bin/ Compiled platform binaries
+-- jux-launcher/ Native launcher (C, spawns JVM on non-main thread)
+-- demo/ Example application
+-- gradle/
| +-- libs.versions.toml Version catalog
+-- build.gradle Root build with native + Java orchestration
+-- CLAUDE.md Full architecture guide
- JDK 25 (with preview features)
- Gradle 9.x
- Rust (stable toolchain) — for building the native library
- CMake — for building the native launcher
- Windows: WebView2 runtime (ships with Windows 10/11)
- Linux:
libwebkit2gtk-4.1-dev - macOS: Xcode command-line tools
# Full build: native (Rust + CMake) + Java
./gradlew buildAll
# Java only (uses cached native binaries)
./gradlew build -PskipNative
# Native only (no Java)
./gradlew buildAll -PnativeOnly
# Run the demo
./gradlew :demo:run -PskipNative
# Run tests
./gradlew test--enable-preview --enable-native-access=ALL-UNNAMED
These are configured automatically in the demo's build.gradle.
OS Main Thread
+-- tao/Win32/AppKit/GTK event loop
+-- Upcalls arrive here -> immediately handed to virtual threads
Virtual Thread Pool
+-- All IPC command handlers
+-- All event handlers (window, DOM, lifecycle)
+-- Free to block: JDBC, HTTP, Files, sleep
Rules:
- Never block the event loop thread
- IPC commands automatically run on virtual threads
Platform.runLater()schedules work on the UI thread- Observable property listeners fire on virtual threads for native events
- All native handles are thread-safe (
AtomicLong+PointerRegistry)
Every native handle is tracked by PointerRegistry — a PhantomReference + ReferenceQueue system that guarantees:
- Exactly-once destruction via
AtomicLong.getAndSet(0) - GC safety net — abandoned handles are cleaned up automatically
- Thread-safe close — concurrent
close()from N threads is safe - Zero leaks — verified by
PointerRegistry.activeCount()in tests
Native types (Pointer, MemorySegment, Arena) never appear in public API. Users interact with Window, Document, Element — never with handles.
| Visibility | Pattern | Contents |
|---|---|---|
| Public API | xss.it.jux.{module}.* |
Interfaces, records, builders — what users import |
| Internal | com.xss.it.jux.{module}.* |
Handles, FFI, memory management — never exported |
Enforced by JPMS module-info.java in every module.
| Component | Minimum |
|---|---|
| Java | JDK 25 |
| Gradle | 9.x (Groovy DSL) |
| Rust | Stable toolchain |
| Windows | 10+ (WebView2 runtime) |
| macOS | 11+ (WebKit) |
| Linux | WebKitGTK 4.1+ |
Jux Toolkit is dual-licensed:
- AGPL-3.0 — Free for open-source projects. Your entire application must be open-source under a compatible license. See LICENSE.
- Commercial License — For proprietary/closed-source applications. See LICENSE-COMMERCIAL.md for details.
Open-source? Use it freely. Building a proprietary app? You need a commercial license.
Copyright (c) 2026 XTREME SOFTWARE SOLUTIONS.
Built with Panama FFI, virtual threads, and native webviews.




