For a long time, JavaFX has included a built-in WebView component. It’s a solid solution for rendering web content within Java applications. However, when it comes to more demanding use cases, it may not be sufficient. That’s why many developers turn to alternatives like JxBrowser.

In this migration guide, we explain how to migrate from JavaFX WebView to JxBrowser, give you code examples and links to the related JxBrowser documentation.

This guide is about how to migrate to JxBrowser. To learn more about why, check out the JxBrowser or JavaFX WebView article, where we explain the technical and architectural differences between the solutions.

Dependencies 

Adding JxBrowser to the project is as simple as adding a bunch of JAR files to the classpath. For example, a JavaFX application that runs on Windows will need the following files:

  • jxbrowser-8.9.2.jar. This file contains most of the JxBrowser API.
  • jxbrowser-javafx-8.9.2.jar. This file contains the JavaFX component of JxBrowser.
  • jxbrowser-win64-8.9.2.jar. This file contains Chromium binaries for 64-bit Windows.

You can download the necessary files from the  JxBrowser 8.9.2 release notes.

If you’re using plain Maven or Gradle, you can add JxBrowser the usual way by adding dependencies from our Maven repo:

Maven
Gradle
<repositories>
    <repository>
        <id>com.teamdev</id>
        <url>https://europe-maven.pkg.dev/jxbrowser/releases</url>
    </repository>
</repositories>
<dependency>
    <groupId>com.teamdev.jxbrowser</groupId>
    <artifactId>jxbrowser-javafx</artifactId>
    <version>8.9.2</version>
</dependency>
<dependency>
    <groupId>com.teamdev.jxbrowser</groupId>
    <artifactId>jxbrowser-win64</artifactId>
    <version>8.9.2</version>
</dependency>
plugins {
    id("com.teamdev.jxbrowser") version "2.0.0"
}
jxbrowser {
    version = "8.9.2"
}
dependencies {
    implementation(jxbrowser.javafx)
    implementation(jxbrowser.win64)
}

Thread safety 

The WebView and WebEngine are not thread-safe; one must always access them and their DOM/JavaScript objects from the JavaFX Application thread only.

JxBrowser is thread-safe. You can safely use JxBrowser objects from different threads. However, be cautious when calling the JxBrowser API from the JavaFX Application thread, as many of its methods are blocking. To avoid glitches in user experience, we generally recommend not calling JxBrowser in the JavaFX Application thread.

Migration 

Creating the browser 

JavaFX provides a visual WebView component that you can add to the scene and a non-visual WebEngine that contains the actual browser API.

Here’s how you create and work with it:

WebView webView = new WebView();
WebEngine webEngine = webView.getEngine();
webEngine.load("https://example.com");
scene.getRoot().getChildren().add(webView);

JxBrowser also consists of visual and non-visual parts. The library provides non-visual Engine and Browser entities that contain the browser API, and a visual BrowserView component that you can add to the scene to display the loaded web content.

// Non-visual part.
Engine engine = Engine.newInstance(HARDWARE_ACCELERATED);
Browser browser = engine.newBrowser();
browser.navigation().loadUrl("https://example.com");

// Visual part:
Platform.runLater(() -> {
    BrowserView browserView = BrowserView.newInstance(browser);  
    scene.getRoot().getChildren().add(browserView);
});

In the example above, we create a non-visual Engine instance that represents the main Chromium process. Then, we create a Browser, another non-visual entity that represents a particular browser in the main process — similar to a browser tab in Google Chrome. Finally, we create a visual BrowserView node and add it to the scene.

Tip: Just as with tabs in Google Chrome, you can create multiple Browser objects within the same Engine.

Check out the Architecture guide to learn more about the main entities in JxBrowser API, the process model, and other architectural details.

Closing the browser 

In JavaFX, there’s no requirement to close the WebView instance. Removing it from the scene graph seems to be sufficient.

In JxBrowser, removing BrowserView from the scene graph will not close the browser and release all allocated resources. You must close the Browser and Engine objects yourself:

JavaFX
JxBrowser
scene.getRoot().getChildren().add(webView);
// Close a single browser.
browser.close();

// Close the engine. It will automatically close all the browsers within it.
engine.close();

The navigation in WebEngine can be almost directly translated to JxBrowser calls:

JavaFX
JxBrowser
webEngine.load("https://example.com");
webEngine.reload();

WebHistory history = webEngine.getHistory();
var currentIndex = history.getCurrentIndex();
var historySize = history.getEntries().size();

// Go back in history.
var previousPage = currentIndex - 1;
if (previousPage >= 0 && previousPage < historySize) {
    history.go(previousPage);
}

// Go forward in history.
var nextPage = currentIndex + 1;
if (nextPage < historySize) {
    history.go(nextPage);
}
Navigation navigation = browser.navigation();
navigation.loadUrl("https://example.com");
navigation.reload();

navigation.goBack();
navigation.goForward();

// Go to an arbitrary entry in history.
navigation.goToIndex(2);

In both solutions, loading occurs in the background, and you need to register  listener to detect when the loading is complete.

In JavaFX, you do that by listening to the state of the load worker:

var worker = webEngine.getLoadWorker();
worker.stateProperty().addListener((ov, oldState, newState) -> {
    if (newState == State.SUCCEEDED) {
        // You can use JavaScript and acces the DOM tree here.
    } else {
        System.out.println("Navigation failed!");
    }
});

In JxBrowser, you the notifications are more granular:

// This event is emitted when the navigation has finished.
// But the frame and DOM tree may not not initialized yet.
navigation.on(NavigationFinished.class, event -> {
    if (event.error() != OK) {
        System.out.println("Navigation failed!");
    }
});

// This event is emitted when the document of the frame
// is loaded and you can work with it.
navigation.on(FrameDocumentLoadFinished.class, event -> {
    // You can use JavaScript and acces the DOM tree here.
});

// This callback allows you to execute JavaScript when the frame
// has just finished loading, but **before** it executed any of its
// own JavaScript.
browser.set(InjectJsCallback.class, params -> {
    Frame frame = params.frame();
    JsObject window = frame.executeJavaScript("window");
    if (window != null) {
        ...
    }
    return Response.proceed();
});

There are nine granular navigation events in JxBrowser. Check the complete list.

Calling JavaScript from Java 

In both solutions, you can execute arbitrary JavaScript, obtain JavaScript objects in Java, and enjoy automatic type conversion:

JavaFX
JxBrowser
// The JavaScript object is converted to JSObject.
JSObject dialogs = (JSObject) webEngine.executeScript("dialogs");
dialogs.call("showError", "The card number is not correct!");

// The JavaScript string is converted to String.
String locale = (String) dialogs.getMember("locale");
browser.mainFrame().ifPresent(frame -> {
    // The JavaScript object is converted to JsObject.
    JsObject dialogs = frame.executeJavaScript("dialogs");
    jsObject.call("showError", "The card number is not correct!");

    // The JavaScript string is converted to String.
    String locale = dialogs.property("locale");
});

JavaFX automatically converts incoming JavaScript values. The primitive types are converted to their Java counterparts, and the JavaScript objects are converted the JSObject instances.

JxBrowser performs a similar conversion but provides dedicated Java types for specific JavaScript types, such as function, Promise, ArrayBuffer, and others. Check the complete list in the type conversion guide.

JxBrowser has no direct replacement for JSObject.getSlot() and JSObject.setSlot() methods for accessing indexed JavaScript objects.

JavaScript objects lifecycle 

In both JavaFX and JxBrowser, the JSObject and JsObject instances remain functional as long as their JavaScript counterparts are alive. A JavaScript object dies when it gets garbage collected or when a new document gets loaded in the frame. An attempt to use a dead JavaScript object will throw an exception in both JavaFX and JxBrowser.

It’s hard to predict when JavaScript objects are garbage collected, so in JxBrowser, the JavaScript objects passed to Java are protected from garbage collection and are only closed when the new document gets loaded. To release a reference to the JavaScript object and let the garbage collector have it, call the JsObject.close() method:

JsObject persistentObject = frame.executeJavaScript("dialogs");
persistentObject.close();

Calling Java from JavaScript 

To call Java code from JavaScript, you need to inject a Java object into the JavaScript world. That is very similar in both solutions:

JavaFX
JxBrowser
public static class GreetingService {
    public void greet(String name) {
        System.out.println("Hello, " + name + "!");
    }
}

...

GreetingService greetings = new GreetingService();
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("greetings", greetings);
@JsAccessible
public static class GreetingService {
    public void greet(String name) {
        System.out.println("Hello, " + name + "!");
    }
}

...

GreetingService greetings = new GreetingService();
JsObject window = frame.executeScript("window");
window.putProperty("greetings", greetings);

Members accessibility 

In JavaFX, you can access any public member of an injected Java object from JavaScript.

In JxBrowser, you need to mark Java classes and their members as JavaScript-accessible explicitly:

// All public members of the class will be accessible.
@JsAccessible
public class AccessibleClass {
    public String sayHelloTo(String firstName) {
        ...
    }
}

// Only a single method of the class will be accessible.
public class RestrictedClass {

    @JsAccessible
    public String sayHelloTo(String firstName) {
        ...
    }
}

In cases where you can’t annotate a class, like with the standard library classes, use this approach:

JsAccessibleTypes.makeAccessible(java.util.HashMap.class);

Read more about how to make objects accessible from JavaScript in the JavaScript guide.

Java objects lifecycle 

JavaFX uses weak references for the Java objects passed to JavaScript. That means an object can be garbage collected, after which its JavaScript counterpart will become undefined.

JxBrowser uses strong references and prevents Java objects from being collected. The references are removed when the new document is loaded in the frame, the frame is removed, or when the browser is closed.

Proxy 

The JavaFX WebView uses the networking stack of the Java runtime and, therefore, automatically respects the Java proxy configuration.

In JxBrowser, Chromium uses its own networking in a separate process and respects the system proxy settings. If you don’t want to use the system settings, you can configure a proxy separately for each profile:

JavaFX
JxBrowser
// Configure proxy settings.
System.setProperty("http.proxyHost", "proxy.com");
System.setProperty("http.proxyPort", "8080");
System.setProperty("https.proxyHost", "proxy.com");
System.setProperty("https.proxyPort", "8081");
System.setProperty("nonProxyHosts", "example.com|microsoft.com");

// Configure the proxy authentication.
Authenticator.setDefault(new Authenticator() {
    protected PasswordAuthentication getPasswordAuthentication() {
        return new PasswordAuthentication("username", "password".toCharArray());
    }
});
// Configure proxy settings.
var profile = engine.profiles().defaultProfile();
var exceptions = "example.com,microsoft.com";
var proxyRules = "http=proxy.com:8080;https=proxy.com:8081";
profile.proxy().config(CustomProxyConfig.newInstance(proxyRules, exceptions));

// Configure the proxy authentication.
profile.network().set(AuthenticateCallback.class, (params, tell) -> {
   if (params.isProxy()) {
       tell.authenticate("username", "password");
   } else {
       // Skip other authentication requests.
       tell.cancel();
   }
});

DOM Access 

JavaFX and JxBrowser provide a similar set of features to access the DOM tree:

JavaFX
JxBrowser
var document = webEngine.getDocument();
var element = document.getElementById("exit-app");
((EventTarget) element).addEventListener("click", listener, false);
browser.mainFrame()
       .flatMap(Frame::document)
       .flatMap(document -> document.findElementById("exit-app"))
       .ifPresent(element -> {
           element.addEventListener(CLICK, listener, true);
        });

In JxBrowser, the DOM nodes are automatically converted to their respective types when they cross the Java-JavaScript boundary. Similar to the JsObject, they are protected from garbage collection in the browser and may call the close() method to close them and make them collectable.

Read more about the access to the DOM in the DOM guide.

Printing 

JavaFX provides an API to print any visual node, including WebView. You can select a printer, configure some of the printing settings programmatically, or display a system printing dialog to the user.

JxBrowser uses Chromium’s printing capabilities. It also allows you to select the printer, programmatically configure the printing settings, and show the Chromium’s Print Preview dialog if needed.

JavaFX
JxBrowser
var printer = findMyPrinter(Printer.getAllPrinters());
var job = PrinterJob.createPrinterJob(printer);
if (showDialogs) {
    // Show system dialogs to the user. 
    job.showPageSetupDialog(stage);
    job.showPrintDialog(stage);
} else {
    // Or print silently.
    var settings = printerJob.getJobSettings();
    settings.setCopies(3);
    settings.setCollation(COLLATED);

    webView.getEngine().print(job);
    printerJob.job();
}
browser.set(PrintCallback.class, (params, tell) -> {
    if (showDialogs) {
        // Show system dialogs to the user. 
        tell.showPrintPreview();
    } else {
        // Or print silently.
        tell.print();
    }
});

// Register `PrintHtmlCallback` for printing HTML pages.
// When printing **from** a PDF file, use `PrintPdfCallback`.
browser.set(PrintHtmlCallback.class, (params, tell) -> {
    var printer = findMyPrinter(params.printers());

    var job = printer.printJob();
    var settings = job.settings();
    settings.copies(3);
    settings.enableCollatePrinting();
    job.on(PrintCompleted.class, event -> {
        System.out.println("Printing completed");
    });
    tell.proceed(printer);
});

browser.set(PrintPdfCallback.class, (params, tell) -> {
    ...
});

Tip: You can always use the Chromium’s built-in PDF printer — even if there are no system printers: params.printers().pdfPrinter().

Read more about how to configure printing in the Printing guide.

Print preview in JxBrowser

The print preview dialog in JxBrowser.

User agent 

In JavaFX, you can customize the user agent of the browser.

In JxBrowser, you can customize both the user agent of a single browser and the entire engine:

JavaFX
JxBrowser
webEngine.setUserAgent("custom user agent");
// Configure the global UA when the engine starts.
var opts = EngineOptions
        .newBuilder(HARDWARE_ACCELERATED)
        .userAgent("custom user agent")
        .build();
var engine = Engine.newInstance(opts);

// Or, configure UI for the specific browser.
browser.userAgent("custom user agent");

User data directory 

In JavaFX, the user data directory is used to store the data from the local storage. You can configure it explicitly, or the engine will automatically pick it according to the operating system and user preferences.

In JxBrowser, the user data directory stores all user data, including caches, local storage, and other related information. You can configure it when starting the engine, or JxBrowser will use the temporary directory, which will be deleted when the Engine is closed:

JavaFX
JxBrowser
webEngine.setUserDataDirectory(new File("/path/to/directory"));
var opts = EngineOptions
        .newBuilder(HARDWARE_ACCELERATED)
        .userDataDir(Paths.get("/path/to/directory"))
        .build();
var engine = Engine.newInstance(opts);

Note that the same user data directory cannot be used simultaneously by multiple Engine instances running in a single or different Java application. An attempt to use the same user data directory will lead to an exception during the Engine creation.

Pop-up windows 

In JavaFX, when the web page wants to open something in a new window, WebEngine will not create a new window. Instead, it will replace the currently loaded page with the new one.

By registering a custom pop-up handler, you can change this behavior:

webEngine.setCreatePopupHandler(features -> {
    if (noPopups) {
        // Returning null cancels the pop-up creation.
        return null;
    }
    // By creating a new WebView, you can instruct JavaFX
    // to use it for the new pop-up.
    var popupView = new WebView();
    scene.getRoot().getChildren().add(popupView);
    return popupView.getEngine();
});

In JxBrowser, all pop-ups are suppressed by default. To change that, register the CreatePopupCallback:

browser.set(CreatePopupCallback.class, params -> {
    return noPopups
                ? CreatePopupCallback.Response.suppress()
                : CreatePopupCallback.Response.create();
    }
});

If the pop-up creation is allowed and BrowserView is visible in the UI, JxBrowser will open the pop-up in a new Stage. You can customize this behavior in the OpenBrowserPopupCallback:

browser.set(OpenBrowserPopupCallback.class, params -> {
    var popupBrowser = params.popupBrowser();
    var popupBounds = params.initialBounds();
        Platform.runLater(() -> {
        var popupView = BrowserView.newInstance(browser);
        scene.getRoot().getChildren().add(popupView);
    });
    return OpenBrowserPopupCallback.Response.proceed();
});

Read more about handling pop-up dialogs in the Pop-ups guide.

JavaScript dialogs 

Both JavaFX and JxBrowser allow you to customize the behavior of JavaScript dialogs, such as confirm, prompt, and alert:

JavaFX
JxBrowser
webEngine.setConfirmHandler(value -> {
    if (silent) {
        return null;
    } else {
        return showMyConfirmDialog();
    }
});

webEngine.setPromptHandler(promptData -> {
    if (silent) {
        return null;
    } else {
        return showMyPromptDialog(promptData);
    }
});

webEngine.setOnAlert(event -> System.out.println("Alert happened!"));
browser.set(ConfirmCallback.class, (params, action) -> {
    if (silent) {
        action.cancel();
    } else {
        var result = showMyConfirmDialog(params);
        if (result) {
            action.ok();
        } else {
            action.cancel();   
        } 
    }
});

browser.set(PromptCallback.class, (params, action) -> {
    if (silent) {
        action.cancel();
    } else {
        action.ok(showMyPromptDialog(params));
    }
});

browser.set(AlertCallback.class, (params, action) -> {
    System.out.println("Alert happened");
    action.ok();
});

If you don’t configure these handlers, JavaFX will suppress the dialogs by default — with a false value for the confirm dialog, and an empty string for the prompt dialog. JxBrowser, on the other hand, will open the JavaFX implementations of the dialogs.

Read how to customize file choosers, authentication, and other kinds of dialogs in the Dialogs guide.

Custom CSS 

In JavaFX, you can inject custom CSS styles by configuring the path to a file or composing a data URL with the styles.

In JxBrowser, you can inject custom CSS styles by passing the styles as a string:

JavaFX
JxBrowser
webEngine.setUserStyleSheetLocation("file:///path/theme.css");
// This callback is invoked when the document is ready
// and CSS can be injected.
browser.set(InjectCssCallback.class, params -> {
    var styles = readFile("file:///path/theme.css")
    return InjectCssCallback.Response.inject(styles);
});

Conclusion 

Both JavaFX WebView and JxBrowser offer a comparable set of features, and migrating from WebView to JxBrowser will not cause you any trouble.

In this guide, we provided code examples for migrating most of the WebView functionality, and added links to the related documentation.

Although real-world migration can be difficult, we believe it doesn’t need to be. Use this guide as the launchpad for your migration project and contact us if you have any questions.

Spinner

Sending…

Sorry, the sending was interrupted

Please try again. If the issue persists, contact us at info@teamdev.com.

Read and agree to the terms to continue.

Your personal JxBrowser trial key and quick start guide will arrive in your Email Inbox in a few minutes.