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:
<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 sameEngine
.
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:
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();
Navigation
The navigation in WebEngine
can be almost directly translated to JxBrowser calls:
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);
Navigation listeners
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:
// 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:
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:
// 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:
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.
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.
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:
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:
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:
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:
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.
Sending…
Sorry, the sending was interrupted
Please try again. If the issue persists, contact us at info@teamdev.com.
Your personal JxBrowser trial key and quick start guide will arrive in your Email Inbox in a few minutes.