In this tutorial, we’ll demonstrate how to use the jDeploy desktop lib to add deep-linking support to a JavaFX desktop application. We’ll configure jDeploy by editing package.json directly.
|
Tip
|
This tutorial is also available in variants that show how to configure jDeploy using the GUI application or the MCP server with AI tools. A Swing version is also available. |
Background
This tutorial is a follow-up to The Desktop Companion App Pattern, which introduced the concept of using custom URL schemes to enable communication between web applications and desktop apps.
In that article, we demonstrated basic URL scheme handling, but there was a limitation: on Windows and Linux, opening a custom URL would launch a new process each time, with the URL passed as a command-line argument. This wasn’t ideal for most use cases, as it required managing multiple instances or manually implementing single-instance behavior.
jDeploy 6.0 introduces singleton mode, which ensures only one instance of your application runs at a time. When a custom URL is opened, the existing instance receives the URL via a callback — the same behavior macOS provides natively. This makes the companion app pattern much more practical, enabling simple inter-process communication between your web app and desktop app without spawning duplicate processes.
What is Deep Linking?
Deep linking allows users to navigate directly to specific content or functionality within an application using a URL. This is particularly useful for desktop applications, as it enables users to access specific features or data without having to navigate through the application’s interface.
Some examples of deep linking include:
-
A user clicking on a link in an email that opens a specific page within a desktop application.
-
A user sharing a link to a specific feature or content within a desktop application on social media.
-
A background service or web dashboard opening a specific view in the desktop app via a custom URL.
Prerequisites
This tutorial assumes you have:
-
A basic JavaFX application
-
jDeploy 6.0 or later installed
-
Familiarity with Maven or Gradle for dependency management
Overview
To add deep linking to your JavaFX application, you need to:
-
Add the jdeploy-desktop-lib-javafx dependency to your project
-
Configure a custom URL scheme and singleton mode in your
package.json -
Implement a
JDeployOpenHandlerto receive and handle deep link events
Add the Dependency
Add the jdeploy-desktop-lib-javafx dependency to your project.
Gradle:
dependencies {
implementation 'ca.weblite:jdeploy-desktop-lib-javafx:1.0.2'
}
Maven:
<dependency>
<groupId>ca.weblite</groupId>
<artifactId>jdeploy-desktop-lib-javafx</artifactId>
<version>1.0.2</version>
</dependency>
Configure jDeploy
In your package.json, configure a custom URL scheme and enable singleton mode. Singleton mode ensures that when a user clicks a deep link, the existing application instance handles it rather than launching a new instance.
{
"name": "my-app",
"version": "1.0.0",
"jdeploy": {
"javaVersion": "11",
"jar": "target/my-app.jar",
"title": "My App",
"singleton": true,
"urlSchemes": ["myapp"]
}
}
The urlSchemes array registers your custom URL schemes with the operating system. In this example, URLs like myapp://settings or myapp://user/123 will open your application.
Implement the Open Handler
The JDeployOpenHandler interface defines three callback methods that your application implements to handle external events:
-
openFiles(List<File> files)— Called when files should be opened (e.g., double-click, drag-drop, or "Open With") -
openURIs(List<URI> uris)— Called when custom URL schemes are activated (deep links) -
appActivated()— Called when the application should be brought to the foreground
Register your handler early in your application’s initialization using JDeployFXApp.setOpenHandler(). All callbacks are dispatched on the JavaFX Application Thread, so you can safely update your UI directly.
import ca.weblite.jdeploy.app.JDeployOpenHandler;
import ca.weblite.jdeploy.app.javafx.JDeployFXApp;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.io.File;
import java.net.URI;
import java.util.List;
public class MyApp extends Application {
private Stage primaryStage;
@Override
public void start(Stage stage) {
this.primaryStage = stage;
StackPane root = new StackPane();
Scene scene = new Scene(root, 800, 600);
stage.setTitle("My App");
stage.setScene(scene);
// Register the deep link handler
JDeployFXApp.setOpenHandler(new JDeployOpenHandler() {
@Override
public void openFiles(List<File> files) {
for (File file : files) {
openDocument(file);
}
}
@Override
public void openURIs(List<URI> uris) {
for (URI uri : uris) {
handleDeepLink(uri);
}
}
@Override
public void appActivated() {
// Bring window to front
primaryStage.setIconified(false);
primaryStage.toFront();
primaryStage.requestFocus();
}
});
stage.show();
}
private void openDocument(File file) {
System.out.println("Opening file: " + file.getAbsolutePath());
// Load and display the file content
}
private void handleDeepLink(URI uri) {
System.out.println("Deep link received: " + uri);
// Route to the appropriate view based on the URI
}
public static void main(String[] args) {
launch(args);
}
}
Handle Deep Link URIs
When a deep link is received, parse the URI to determine what action to take. The URI structure typically follows this pattern:
myapp://host/path?query=value#fragment
For example:
-
myapp://settings— Open the settings page -
myapp://user/123— Open user profile with ID 123 -
myapp://document?id=abc&mode=edit— Open document "abc" in edit mode
Here’s how to parse and route deep links:
private void handleDeepLink(URI uri) {
String scheme = uri.getScheme(); // "myapp"
String host = uri.getHost(); // "settings", "user", "document"
String path = uri.getPath(); // "/123", "", etc.
String query = uri.getQuery(); // "id=abc&mode=edit"
switch (host) {
case "settings":
showSettingsPanel();
break;
case "user":
if (path != null && path.length() > 1) {
String userId = path.substring(1); // Remove leading "/"
showUserProfile(userId);
}
break;
case "document":
Map<String, String> params = parseQueryString(query);
String docId = params.get("id");
String mode = params.getOrDefault("mode", "view");
openDocument(docId, mode);
break;
default:
System.out.println("Unknown deep link: " + uri);
}
}
private Map<String, String> parseQueryString(String query) {
Map<String, String> params = new HashMap<>();
if (query != null && !query.isEmpty()) {
for (String param : query.split("&")) {
String[] keyValue = param.split("=", 2);
if (keyValue.length == 2) {
params.put(keyValue[0], keyValue[1]);
}
}
}
return params;
}
Complete Example
Here’s a complete example that demonstrates deep linking with a tabbed interface. Clicking a deep link switches to the corresponding tab.
import ca.weblite.jdeploy.app.JDeployOpenHandler;
import ca.weblite.jdeploy.app.javafx.JDeployFXApp;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.File;
import java.net.URI;
import java.util.List;
public class DeepLinkDemo extends Application {
private Stage stage;
private TabPane tabPane;
@Override
public void start(Stage primaryStage) {
this.stage = primaryStage;
// Create tabbed pane with sample tabs
tabPane = new TabPane();
tabPane.getTabs().addAll(
createTab("Home", "Welcome to the Home tab"),
createTab("Settings", "Application Settings"),
createTab("Profile", "User Profile")
);
// Add instructions
Label instructions = new Label(
"Try these deep links in your browser:\n" +
"• demo://home\n" +
"• demo://settings\n" +
"• demo://profile"
);
instructions.setPadding(new Insets(10));
VBox root = new VBox(tabPane, instructions);
Scene scene = new Scene(root, 600, 400);
// Register deep link handler
JDeployFXApp.setOpenHandler(new JDeployOpenHandler() {
@Override
public void openFiles(List<File> files) {
// Handle file opens if needed
}
@Override
public void openURIs(List<URI> uris) {
for (URI uri : uris) {
navigateTo(uri.getHost());
}
}
@Override
public void appActivated() {
stage.setIconified(false);
stage.toFront();
stage.requestFocus();
}
});
primaryStage.setTitle("Deep Link Demo");
primaryStage.setScene(scene);
primaryStage.show();
}
private void navigateTo(String destination) {
int tabIndex = switch (destination) {
case "home" -> 0;
case "settings" -> 1;
case "profile" -> 2;
default -> -1;
};
if (tabIndex >= 0) {
tabPane.getSelectionModel().select(tabIndex);
}
}
private Tab createTab(String title, String content) {
Label label = new Label(content);
label.setStyle("-fx-font-size: 18px;");
StackPane pane = new StackPane(label);
pane.setAlignment(Pos.CENTER);
Tab tab = new Tab(title, pane);
tab.setClosable(false);
return tab;
}
public static void main(String[] args) {
launch(args);
}
}
The package.json for this example:
{
"name": "deep-link-demo",
"version": "1.0.0",
"jdeploy": {
"javaVersion": "17",
"jar": "target/deep-link-demo.jar",
"title": "Deep Link Demo",
"singleton": true,
"urlSchemes": ["demo"]
}
}
How It Works
Singleton Mode
When singleton mode is enabled, only one instance of your GUI application runs at a time. If the user launches the app again — whether by double-clicking a file, clicking a custom URL, or running the launcher command — the existing window is activated and the file or URI is forwarded to it.
On Windows and Linux, jDeploy uses file-based IPC to communicate between the secondary launch and the primary instance. On macOS, the operating system handles single-instance behavior natively through the Desktop API.
Event Queueing
If deep link events arrive before your handler is registered, they are queued and delivered as soon as you call setOpenHandler(). This ensures you never miss events during application startup.
Thread Safety
All callbacks from JDeployFXApp are dispatched on the JavaFX Application Thread. You can safely update your UI components directly in the handler methods without wrapping them in Platform.runLater().
Summary
Adding deep linking to a JavaFX application with jDeploy involves:
-
Add the dependency —
ca.weblite:jdeploy-desktop-lib-javafx:1.0.2 -
Configure jDeploy — Edit
package.jsonto setsingleton: trueand defineurlSchemes -
Implement
JDeployOpenHandler— Register handlers for files, URIs, and app activation -
Parse and route URIs — Extract host, path, and query parameters to navigate your app
With these steps, your desktop application can respond to deep links from emails, web pages, other applications, and system tray helpers, providing a seamless user experience.
See Also
-
Singleton Mode in the jDeploy Manual
-
Custom URL Schemes in the jDeploy Manual
-
jDeploy 6.0 Release Notes for multi-modal app support
-
Deep Linking with Swing for the Swing version of this tutorial