Skip to content

Viewer JavaFX RI

Patrick Corless edited this page Oct 18, 2026 · 2 revisions

The icepdf JavaFX viewer is a reference implementation of a PDF viewer built on JavaFX. It comes in two layers: PdfView, an embeddable JavaFX Control for viewing, annotating and filling in PDF documents, and PdfViewer, a complete viewer assembled from it with a header bar, tool rail, side panels, dialogs, printing and signing. You can run the viewer as a stand-alone application, drop PdfViewer into your own window, or build your own interface around a bare PdfView.

Pages are rendered by the icepdf core's Java2D engine into tiles that are shared with JavaFX without copying. Everything interactive, such as text selection, search highlights, annotation handles and popups, and form field editors, is a native JavaFX node.

The JavaFX viewer requires Java 17 and JavaFX 22 or later. It lives alongside the Swing/AWT viewer, which remains available.

Maven dependencies

<dependency>
    <groupId>com.github.pcorless.icepdf</groupId>
    <artifactId>icepdf-core</artifactId>
    <version>x.y.z</version>
</dependency>
<dependency>
    <groupId>com.github.pcorless.icepdf</groupId>
    <artifactId>icepdf-viewer-fx</artifactId>
    <version>x.y.z</version>
</dependency>

icepdf-viewer-fx holds the page view, the viewer component and the viewer application. JavaFX is not included; your application brings it (javafx-base, javafx-graphics and javafx-controls, version 22 or later).

The module name is org.icepdf.fx. It requires org.icepdf.core and javafx.controls.

Running the Viewer

From a source checkout, Gradle builds the core and starts the viewer:

./gradlew :viewer:viewer-fx:run
./gradlew :viewer:viewer-fx:run --args="/path/file.pdf" -Pxmx=4g

Every file named on the command line opens as a tab of the first window. Arguments can be file paths or file: URIs.

To build a single runnable jar with JavaFX for your platform included:

./gradlew :viewer:viewer-fx:jarWithDependencies
java -jar viewer/viewer-fx/build/libs/icepdf-viewer-fx-<version>-jar-with-dependencies.jar [file.pdf]

With Maven, install the core first, then run the viewer with the JavaFX plugin:

mvn -f core/core-awt/pom.xml install -DskipTests -Djacoco.skip=true
mvn -f viewer/viewer-fx/pom.xml javafx:run

On Linux, ./gradlew :viewer:viewer-fx:launcherLibs followed by packaging/linux/install-desktop.sh --fx installs a desktop launcher.

Embedding the Viewer

There are three ways to embed the viewer, from the most provided for you to the most control:

Approach What you get Start here when
PdfViewer The complete viewer as a single JavaFX node You want a full-featured viewer with little code
PdfViewer without chrome The viewer's document area, commands and panels, driven from your own toolbar You want your own toolbar and layout but the viewer's behaviour
PdfView The bare page view and its events You are building your own interface from scratch

Whichever you choose, load the system fonts once at startup so documents that don't embed their fonts render correctly:

FontSettings.apply();   // cached in ~/.icepdf/fx, scanned in the background the first time

FontSettings.apply(cacheFile, extraDirectories) lets you keep the font cache with your application's own files.

A runnable example of the first approach is in examples/javafx.

1. The complete viewer

PdfViewer is a JavaFX node containing the page view, header bar, tool rail, side panels, dialogs, printing and signing:

PdfViewer viewer = PdfViewer.create();              // every tool and panel
viewer.setOnLink(getHostServices()::showDocument);  // links open in the system browser
stage.setScene(new Scene(viewer, 1200, 900));
viewer.open(Path.of("file.pdf"));                   // loads in the background, asks for a password if needed
// ... and when the window closes:
viewer.dispose();

To offer only some of the viewer's features, pass a ViewerFeatures and a UserLayout (see Configuring the viewer):

ViewerFeatures features = ViewerFeatures.builder(ViewerFeatures.Preset.ANNOTATING)
        .add("signature.sign")
        .remove("document.save-as", "annotation.ink")
        .panels(SidePanel.THUMBNAILS, SidePanel.COMMENTS)
        .build();
PdfViewer viewer = PdfViewer.create(features, UserLayout.load(settingsDir.resolve("layout.properties")));

PdfViewer also has hooks for the parts an application usually owns, including setOnNewWindow, setOnExit, setOnPreferences, setFileHistory, setSigningStore, setPrintStore, setOnError and setDialogs.

2. The viewer without its chrome

Leave out the header, tool rail and side panel strip, and drive the viewer from your own controls. ActionControls.bind connects a button or menu item to a viewer command: it runs the command, disables the control when the command can't run, and keeps a toggle's selected state in sync (the current tool or view mode, for example).

PdfViewer viewer = PdfViewer.create(
        ViewerFeatures.builder(ViewerFeatures.Preset.FULL).chrome().build(),  // or chrome(Chrome.HEADER, ...)
        UserLayout.inMemory());
ActionControls actions = viewer.getActions();
Button previous = actions.bind(new Button("Prev"), "navigation.previous");
Button next = actions.bind(new Button("Next"), "navigation.next");
ToggleButton highlight = actions.bind(new ToggleButton("Highlight"), "annotation.highlight");
MenuItem print = actions.bind(new MenuItem("Print…"), "document.print");

// the side panels are still available to place wherever you like
SplitPane split = new SplitPane(viewer.getCurrentDocument().getOutline(), viewer);

Commands that depend on a missing part are left out automatically. For example, Find needs the header and the side panel commands need the panel strip.

3. The bare page view

PdfView alone, with your own toolbar, labels and dialogs. A DocumentSession owns the document: it opens files in the background, asks for passwords, saves safely and disposes the previous document. The viewer's commands work through a ViewerContext, which offers the view's own commands immediately and host commands such as Find, Open or Save once you supply a handler.

PdfView view = new PdfView();
DocumentSession session = new DocumentSession(view);
TextField find = new TextField();
ActionControls actions = new ActionControls(StandardActions.registry(),
        ViewerContext.of(view).onFind(find::requestFocus));

actions.bind(previousButton, "navigation.previous");
actions.bind(nextButton, "navigation.next");
actions.bind(zoomInButton, "view.zoom-in");
find.setOnAction(e -> view.search(find.getText()));
actions.bind(nextHitButton, "search.next");

// every part of the view's state is a JavaFX property
pageLabel.textProperty().bind(Bindings.createStringBinding(
        () -> (view.getCurrentPageIndex() + 1) + " of " + view.getPageCount(),
        view.currentPageIndexProperty(), view.pageCountProperty()));
zoomLabel.textProperty().bind(view.zoomProperty().multiply(100).asString("%.0f%%"));

// opt in to behaviour PdfViewer would otherwise provide
AnnotationContextMenu.install(view, null);   // right-click an annotation: colour, Properties…, Delete
SignatureFlow.install(session);              // click or draw an empty signature field to sign it
actions.installAccelerators(scene, "search.find", "edit.copy", "edit.undo", "edit.redo");

session.open(Path.of("file.pdf"));

Nothing is added to your scene unless you ask for it. When the page has focus, the view handles its own keys (Ctrl+A, Ctrl+C, Ctrl+Z/Y, Delete, arrows, Page Up/Down). installAccelerators makes the shortcuts you choose work from anywhere in your window, while your own text fields keep their normal Ctrl+C.

The view can also display a Document you manage yourself:

Document document = new Document();
document.setSecurityCallback(new PasswordPrompt(stage, "file.pdf"));  // asks for the password, three tries
document.setFile("file.pdf");

view.setDocument(document);                 // dispose it yourself after setDocument(null)
view.setViewMode(ViewMode.CONTINUOUS);      // SINGLE_PAGE, CONTINUOUS, FACING, FACING_CONTINUOUS
view.setFitMode(FitMode.WIDTH);
view.setToolMode(ToolMode.TEXT_SELECT);

Configuring the Viewer

The viewer is configured in two tiers. ViewerFeatures decides what a product offers: which commands, side panels and page layouts exist, and which tools appear on the tool rail by default. UserLayout records how the user has arranged things within those limits. A user can never bring back something the product has removed.

ViewerFeatures

Start from one of four presets and adjust:

Preset Includes
READING Open, print, properties, search, view, navigation and the select and pan tools. No editing.
ANNOTATING Reading, plus the annotation tools, undo/redo, delete and save.
FORMS Reading, plus form filling, form reset, signing and save.
FULL Everything.

Commands are matched by id or by prefix ("annotation.*"). A removal always wins over an addition, regardless of order.

Builder method Description
add(String...) Adds commands by id or pattern.
remove(String...) Removes commands by id or pattern.
panels(SidePanel...) The side panels offered. All panels if never called.
viewModes(ViewMode...) The page layouts offered. All layouts if never called.
defaultRail(String...) The tools on the rail, in order, before the user changes anything.
railCandidates(String...) Which commands may be placed on the rail. Default tool.*, annotation.*, signature.*.
userCustomisable(boolean) Whether users may rearrange the rail. Default true.
multipleDocuments(boolean) Whether the viewer shows several documents as tabs. Default false.
chrome(Chrome...) The chrome shown around the document. chrome() with no arguments shows none.

Features file

The same choices can be read from a properties file with ViewerFeatures.fromProperties. Lists are comma separated, and missing keys keep the preset's value.

features.preset=ANNOTATING
features.add=signature.sign
features.remove=document.save-as, annotation.ink
features.panels=thumbnails, bookmarks, comments
features.viewModes=CONTINUOUS, FACING
features.rail=tool.select, tool.pan, annotation.highlight
features.customisable=true
features.multipleDocuments=true
features.chrome=none
Property Values
features.preset READING, ANNOTATING, FORMS, FULL (default)
features.add, features.remove Command ids or patterns
features.panels thumbnails, bookmarks, comments, attachments, layers, signatures, search
features.viewModes SINGLE_PAGE, CONTINUOUS, FACING, FACING_CONTINUOUS
features.rail Command ids, in order
features.customisable true / false
features.multipleDocuments true / false
features.chrome none, or any of header, tool-rail, side-panels

The stand-alone viewer reads features.properties from its settings directory (see below), or the file named by -Dorg.icepdf.fx.features=/path/features.properties. It turns tabs on unless the file sets features.multipleDocuments=false.

Command ids

Group Ids
Document document.open, document.open-new-tab, document.new-tab, document.save, document.save-as, document.export-redacted, document.print, document.properties, document.close
Application app.new-window, app.preferences, app.about, app.exit
Edit edit.undo, edit.redo, edit.copy, edit.select-all, edit.delete-annotation
Search search.find, search.panel, search.next, search.previous
View view.side-panel, view.comments, view.full-screen, view.zoom-in, view.zoom-out, view.actual-size, view.fit-page, view.fit-width, view.rotate-clockwise, view.rotate-counterclockwise, view.mode.single-page, view.mode.continuous, view.mode.facing, view.mode.facing-continuous, view.cover-page, view.show-annotations, view.highlight-fields
Navigation navigation.first, navigation.previous, navigation.next, navigation.last, navigation.go-to
Tools tool.select, tool.pan
Annotations annotation.highlight, annotation.underline, annotation.strike-out, annotation.note, annotation.free-text, annotation.ink, annotation.rectangle, annotation.ellipse, annotation.line, annotation.redact
Signatures signature.sign
Forms forms.reset

Your own commands can be added by registering them in an ActionRegistry and passing it to the PdfViewer constructor.

UserLayout

UserLayout holds the user's own arrangement: which tools are pinned to the rail and in what order, which are hidden, where the rail and side panel sit, and how the panel opens. Load it from a file with UserLayout.load(path) to keep it between runs, or use UserLayout.inMemory() when it shouldn't be saved.

Moving from Swing viewer settings

A product that already configures the Swing viewer with ViewerPropertiesManager keys can reuse them. ViewerFeatures.fromSwingProperties(properties) maps the Swing viewer's application.* show/hide keys to command ids and panels; any key set to false removes what it hid. Swing settings with no JavaFX counterpart, such as status bar parts, preference tabs and the link and arrow tools, are ignored.

Stand-alone viewer settings

The stand-alone viewer keeps its settings in properties files in ~/.icepdf/fx, or in the directory named by -Dorg.icepdf.fx.viewer.home. These are kept separate from the Swing viewer, which uses the Java Preferences API.

File Contents
viewer.properties Preferences: window placement, view, theme, search options, print defaults, annotation author and colours, signing, imaging and performance settings
layout.properties The user's tool rail and panel arrangement
features.properties Optional product features (see above)

Most of these settings are edited through the Preferences dialog, which has General, Annotations, Search, Imaging, Performance and Fonts tabs.

Look and Feel

Themes

The viewer supports SYSTEM, LIGHT, DARK, HIGH_CONTRAST and HIGH_CONTRAST_DARK modes through viewer.getTheme().modeProperty(). SYSTEM follows the desktop's light or dark setting and its high-contrast themes. The viewer is styled with CSS (viewer.css and pdf-view.css), so an application can override any part of it with its own stylesheet.

Icons

Icons are looked up by id through an IconProvider: command ids (document.print), side panels (panel.comments) and UI parts (ui.menu). The default set is a placeholder set drawn as SVG paths on a 24×24 grid, stroked rather than filled, following the same conventions as Lucide and Tabler so their paths can be dropped in.

To replace some or all of the icons, write a properties file of id=path data and chain it in front of the defaults:

IconProvider icons = SvgIcons.from(myIconsFile).orElse(SvgIcons.placeholders());
PdfViewer viewer = new PdfViewer(features, layout, StandardActions.registry(), icons);

The icon colour comes from the theme (-fx-stroke on the icon-path style class).

Tools and Annotations

The current tool decides what a primary-button drag on a page does. In any mode, a middle-button drag or holding Space while dragging pans, and the mouse wheel or a pinch zooms and scrolls.

Tool Command id Behaviour
TEXT_SELECT tool.select Drag to select text. Annotations can be selected, moved, resized and edited.
PAN tool.pan Drag to pan the view.
HIGHLIGHT annotation.highlight Select text; it is highlighted on release.
UNDERLINE annotation.underline Select text; it is underlined on release.
STRIKE_OUT annotation.strike-out Select text; it is struck out on release.
NOTE annotation.note Click to place a sticky note and type in its popup.
FREE_TEXT annotation.free-text Click to place a text box and type into it.
INK annotation.ink Drag to draw freehand.
RECTANGLE annotation.rectangle Drag to draw a rectangle.
ELLIPSE annotation.ellipse Drag to draw an ellipse.
LINE annotation.line Drag to draw a straight line.
SIGNATURE signature.sign Drag to place a signature field, then sign it.
REDACT annotation.redact Drag over text to mark it, or anywhere else to mark a region, for redaction.

All annotation changes, including creating, moving, resizing, recolouring and deleting, can be undone and redone. Right-clicking an annotation opens a menu to change its colour, open its properties or delete it.

Redaction

The redaction tool marks text and regions but doesn't change the document's content. Export Redacted Copy (document.export-redacted) writes a new copy with the marked content removed and reports what was removed. The viewer warns before closing a document with unsaved redaction marks.

Forms

Form fields are filled in place with native JavaFX editors, and Tab moves between fields in the document's order. From code, PdfView can read and set field values (setFieldValue), reset the form (resetForm), highlight fields (highlightFormFields) and report every change through setOnFormFieldChanged. Form edits take part in undo and redo.

Digital Signatures

Validation

Signatures are checked when a document opens, off the JavaFX thread. Signed fields show a validity badge, and the Signatures side panel lists each one. Clicking a signed field opens SignaturePropertiesDialog, which shows its status (valid, unknown identity or invalid), the individual checks and the certificate chain. From code, PdfView.getSignatures() returns a SignatureStatus for each field, and setOnSignatureClicked lets you handle the click yourself.

Signing

Choose the signature tool and drag to place a new signature field, or click an existing empty one. SignDialog collects the keystore (a PKCS#12 .p12/.pfx file or a PKCS#11 device), the certificate, signer details and the signature's appearance, including a drawn signature. The signed copy is written as an incremental update and opened in place of the original.

Each part also works on its own. DocumentSigning.prepare and saveSigned sign a document as it is saved, including from headless code.

The stand-alone viewer remembers signing settings (signing.* keys in viewer.properties). To create a self-signed PKCS#12 keystore for testing, see PKCS12 keystore setup; the same file works with both viewers.

Printing

PdfPrintDialog.show(view, null) opens a JavaFX print dialog with a page preview. view.print(PrintSettings) prints without a dialog. Pages are printed as vectors through Java2D. The stand-alone viewer remembers the last printer, paper, scaling, orientation and sides.

Encrypted Documents

The view follows the document's permissions. copyAllowed, annotationEditingAllowed and formFillingAllowed report what the user may do, and toolbar buttons can be bound to them. DocumentSession asks for a password when needed; for a Document you open yourself, set a PasswordPrompt (or your own SecurityCallback) before opening it.

Links and Actions

Actions with outside effects, such as URI links, Launch actions, form submission and JavaScript, are left to the application. With PdfViewer, setOnLink receives the links the user clicks. With a bare PdfView, handle them in setOnAnnotationAction:

view.setOnAnnotationAction(e -> { /* inspect e.getAction() */ });

The stand-alone viewer opens http, https and mailto links in the system browser or mail client, and asks before following any other kind of link.

Keyboard Shortcuts

Shortcut is Ctrl on Windows and Linux, and ⌘ on macOS.

Action Shortcut
Open / Open in new tab Shortcut+O / Shortcut+Shift+O
New tab / New window Shortcut+T / Shortcut+N
Save / Save As Shortcut+S / Shortcut+Shift+S
Print Shortcut+P
Document properties Shortcut+D
Close / Exit Shortcut+W / Shortcut+Q
Preferences Shortcut+,
Undo / Redo Shortcut+Z / Shortcut+Shift+Z
Copy / Select all Shortcut+C / Shortcut+A
Find / Search panel Shortcut+F / Shortcut+Shift+F
Next / previous match F3 / Shift+F3
Go to page Shortcut+G
First / last page Home / End
Zoom in / out Shortcut+= / Shortcut+-
Actual size / Fit page / Fit width Shortcut+0 / Shortcut+1 / Shortcut+2
Rotate clockwise / counterclockwise Shortcut+Shift+Plus / Shortcut+Shift+Minus
Show comments Shortcut+Shift+C
Toggle side panel F4
Full screen F11
Render devtools Ctrl+Shift+I (⌘⌥I on macOS)

With the text selection tool and a selection in place, the arrow keys move the caret through the text, Shift extends the selection, Shortcut+←/→ moves by word, and Home/End go to the start or end of the line. Esc clears the selection. The caret crosses page boundaries and skips pages with no text. Without a selection, the arrow keys scroll the view.

Rendering and Performance

Every PdfView and thumbnail strip in the process renders on one shared pool of low-priority daemon threads, which exit when idle. Work for views in the focused window runs first, then visible tiles, annotation layers, page text, previews and thumbnails, in that order. The tile cache is limited to a share of the heap, with on-screen tiles kept.

System property Description
org.icepdf.fx.render.threads Number of render threads. Default is half the cores, between 2 and the cap.
org.icepdf.fx.render.threadPoolCap Maximum number of render threads. Default 8.
org.icepdf.fx.devtools true opens the render devtools window at startup.
org.icepdf.fx.viewer.home The stand-alone viewer's settings directory. Default ~/.icepdf/fx.
org.icepdf.fx.features A features file for the stand-alone viewer.

The core decodes images on its own pools, configured with org.icepdf.core.library.threadPoolSize, imageThreadPoolSize and threadPoolCap. The stand-alone viewer exposes these in the Performance tab of its Preferences dialog.

The render devtools window (Ctrl+Shift+I) shows the render pool's threads and queue, each view's cache and jobs, and the heap, live. It is useful for diagnosing performance on a user's machine.

Internationalization

The JavaFX viewer's text is in org/icepdf/fx/i18n/messages.properties, read through org.icepdf.fx.i18n.Messages. Translations are included for Danish, German, Spanish, Finnish, French, Italian, Dutch, Norwegian, Portuguese, Swedish, and Simplified and Traditional Chinese, covering the whole interface including dialogs and panels. A Swiss German bundle is also present but only partly translated.

The viewer uses the JVM's default locale. To try another language, start it with -Duser.language=fr (add -Duser.country=CN or TW for Chinese). Scripts for checking keys and syncing translations are in viewer/viewer-fx/tools/i18n.

Coming from the Swing Viewer

Swing/AWT viewer JavaFX viewer
SwingController + SwingViewBuilder PdfViewer (complete) or PdfView (bare)
Subclassing SwingViewBuilder to change the UI ViewerFeatures, the chrome options and your own controls bound with ActionControls
ViewerPropertiesManager / Java Preferences ViewerFeatures, UserLayout and properties files in ~/.icepdf/fx
AnnotationCallback setOnAnnotationAction / setOnLink
SecurityCallback / MyGUISecurityCallback DocumentSession or PasswordPrompt
ICEpdfDefault.properties features.properties

Some Swing viewer features are not yet in the JavaFX viewer, including outline and destination editing, text editing, and the link and arrow annotation tools.

Further Reading