How to Build a Java Desktop App with Shadcn UI, React, and JxBrowser

This article demonstrates how to create a cross‑platform Java desktop application by embedding a React + TypeScript UI built with shadcn/ui inside a Chromium‑based JxBrowser view, covering window setup, resource loading for development and production, and two Java‑Web communication strategies (JS‑Java bridge and Protobuf + gRPC).

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
How to Build a Java Desktop App with Shadcn UI, React, and JxBrowser

Swing/JavaFX Limitations

Java native UI toolkits (Swing, JavaFX, SWT) share three drawbacks:

UI pain : implementing modern animated transitions requires custom components.

Ecosystem desert : few component libraries, low community activity, hiring difficulty.

Outdated look : default controls appear decade‑old.

Web UI provides abundant component libraries, built‑in high‑DPI, touch, responsive support, and cross‑platform consistency.

Overall Solution: JxBrowser + React + shadcn/ui

Goal: a preferences dialog that persists user settings to the local file system across restarts. Embed a React + TypeScript UI built with shadcn/ui inside a Chromium‑based JxBrowser view hosted in a Swing JFrame.

var engine = Engine.newInstance(HARDWARE_ACCELERATED);
var browser = engine.newBrowser();
SwingUtilities.invokeLater(() -> {
    var view = BrowserView.newInstance(browser);
    var frame = new JFrame("Application");
    frame.addWindowListener(new WindowAdapter() {
        @Override
        public void windowClosing(WindowEvent e) {
            engine.close();
        }
    });
    frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    frame.add(view, BorderLayout.CENTER);
    frame.setSize(1280, 900);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
});

Resource Loading: Development vs Production

Development Environment

Run a local dev server; the browser loads the UI from http://localhost:[port] with hot‑reloading.

./gradlew startDevServer
if (!AppDetails.isProduction()) {
    browser.navigation().loadUrl("http://localhost:[port]");
}

Production Environment

Production cannot rely on a dev server. JxBrowser’s custom scheme interceptor packages web assets into the JAR and serves them via a jxb:// protocol.

var options = EngineOptions.newBuilder(HARDWARE_ACCELERATED)
    .addScheme(Scheme.of("jxb"), new UrlRequestInterceptor());
var engine = Engine.newInstance(options.build());

When the browser requests jxb://my-app.com, the interceptor returns index.html and subsequent CSS/JS files from the classpath, keeping resources internal while normal HTTPS/API requests remain unaffected.

if (!AppDetails.isProduction()) {
    browser.navigation().loadUrl("http://localhost:[port]");
} else {
    browser.navigation().loadUrl("jxb://my-app.com");
}

Java ↔ Web Communication

The web front‑end must invoke Java code to read/write the preferences file. Two approaches are presented.

Option 1: JS‑Java Bridge (small projects)

JxBrowser allows JavaScript to call annotated Java methods directly.

@JsAccessible
class PrefsService {
    void setFontSize(int size) { }
}
// TypeScript side
declare class PrefsService {
    setFontSize(size: number): void;
}
prefsService.setFontSize(12);

This works for a small API surface; however, as the number of interfaces grows there is no compile‑time checking or IDE auto‑completion, increasing maintenance cost.

Option 2: Protobuf + gRPC (larger projects)

Define the API in .proto files to generate type‑safe Java and TypeScript code.

service PrefsService {
  rpc SetFontSize(FontSize) returns (google.protobuf.Empty);
}
enum FontSize {
  SMALL = 0;
  DEFAULT = 1;
  LARGE = 2;
}

The Java side runs a gRPC server; the web side uses a Connect client.

// Java server
class PrefsService extends PrefsServiceImplBase {
    @Override
    public void setTheme(Theme request, StreamObserver<Empty> responseObserver) { }
}
// TypeScript client
const transport = createGrpcWebTransport({
    baseUrl: `http://localhost:50051`,
});
const prefsClient = createClient(PrefsService, transport);
prefsClient.setFontSize(FontSize.SMALL);

This approach provides type safety, automatic code generation, IDE completion, and compile‑time validation, which become increasingly valuable as the project scales.

References

GitHub repository with the full source code: https://github.com/TeamDev-IP/JxBrowser-Gallery

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaReactgRPCProtobufshadcn/uiJxBrowser
Java Tech Enthusiast
Written by

Java Tech Enthusiast

Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.