Desktop apps with a Java core and a web UI.
JDesk pairs a Java 25 application core with the operating system's own WebView — WebView2, WKWebView, WebKitGTK. The Tauri development model, without Rust, without bundled Chromium.
$ npm create jdesk-app@latest my-appRequires JDK 25+ and your platform’s system WebView.
The small-native trade-off, made for Java teams.
Keep business logic, system integration, lifecycle, and packaging in Java. Build the UI with React, Vue, Svelte, or plain HTML. Ship a small native app.
No bundled Chromium
Render in the WebView already on the machine — WebView2, WKWebView, WebKitGTK. Apps stay small instead of shipping 100+ MB of browser.
No Rust to learn
Small binaries come from a trimmed JVM runtime image via jlink, not a new systems language. If your team knows Java and Gradle, you're ready.
No Node at runtime
The frontend is static files served over the jdesk://app/ scheme. There is no local web server in production, and Node is only a build-time tool.
Compile-time typed IPC
An annotation processor discovers @DesktopCommand methods and generates a typed TypeScript client. No runtime reflection, no glue that drifts.
Secure by default
Every command needs an explicit capability grant. Navigation is locked to the app origin, popups denied, path traversal rejected — enforced in Java.
Native packaging built in
jlink + jpackage produce DMG/PKG, MSI/EXE, and DEB/RPM, each with SHA-256 checksums and a CycloneDX SBOM. Verified on real system WebViews.
Call Java from the web. Get a typed record back.
Annotate a method with @DesktopCommand and JDesk generates a typed client for it. The frontend invokes; the handler runs on a virtual thread, off the UI thread; exactly one result crosses back — correlated by request id.
- Deny-by-default capability check runs before your code.
- Handlers block freely on virtual threads — no async plumbing.
- The generated TypeScript client keeps types in sync at compile time.
@DesktopCommand("greeting.greet")
@RequiresCapability("greeting:use")
public CompletionStage<Response> greet(Request req, InvocationContext ctx) {
String name = req.name().isBlank() ? "world" : req.name().strip();
return completedFuture(new Response("Hello, " + name + "!"));
}import { commands } from "./jdesk-ts/commands";
// Fully typed — the argument and result come from your Java records.
const res = await commands.greeting.greet({ name: "Ada" });
result.textContent = res.message; // "Hello, Ada!"Same trade-off as Tauri, in the language you already ship.
JDesk trades Electron's one-identical-engine guarantee for small apps on each platform's native WebView. If pixel-identical rendering everywhere is a hard requirement, Electron is still the safer choice.
| JDesk | Tauri | Electron | |
|---|---|---|---|
| Backend language | Java (JVM) | Rust | Node.js |
| Renderer | System WebView | System WebView | Bundled Chromium |
| Bundle size | Small (jlink runtime) | Smallest | Large (100+ MB) |
| Runtime dependency | Trimmed JVM (bundled) | None | Bundled Chromium |
| IPC | Compile-time typed | Typed commands | IPC channels |
| Best fit | Java / JVM teams | Rust / native teams | Max web-parity |
Verified on real system WebViews.
Native, packaging, security, and stress tests run on real CI runners and Apple Silicon hardware for every target.
- WebView
- WebView2 (Evergreen)
- Minimum
- Windows 10 1809+
- WebView
- WKWebView
- Minimum
- macOS 13 Ventura+
- WebView
- WebKitGTK 4.1
- Minimum
- Ubuntu 22.04+
Scaffold an app in one command.
Pick a template — basic, React, Vue, Svelte, or a structured multi-module layout — and run it on your OS.
$ npx create-jdesk-app@latest my-app