B4J Tutorial Creating Linux DEB-package for GUI B4J app

This tutorial is made for Linux JDK14 set Java + JavaFX for B4J practically, AI suggestions were partially OK only.
The .jar app file is compiled at Windows developer host.

Building a DEB package for a Java application using JavaFX requires creating a directory structure and writing several configuration files. Here is a detailed algorithm on how to do this for an application named "myb4japp".

▎Step 1: Install the required tools

Make sure you have the required tools installed to build DEB packages:

sudo apt update
sudo apt install build-essential devscripts debhelper

▎Step 2: Create the directory structure

Create the required directory structure for your DEB package:

mkdir -p ~/dev/myb4japp/debian/usr/bin
mkdir -p ~/dev/myb4japp/debian/DEBIAN

▎Step 3: Copy the files

Copy your JAR file and Java libraries to the appropriate directories:

cp ~/dev/myb4japp/myb4japp.jar ~/dev/myb4japp/debian/usr/bin/
cp -r ~/dev/myb4japp/jdk14/* ~/dev/myb4japp/debian/usr/lib/myb4japp/jdk14/

▎Step 4: Create a control file

Create a control file in the DEBIAN directory:

nano ~/dev/myb4japp/debian/DEBIAN/control

Paste the following information into it:

Package: myb4japp
Version: 0.994
Section: education
Priority: optional
Architecture: all
Name=My GUI Linux app
Comment=My First B4J Linux app
Name[gr]=My GUI Linux app on Greek
Comment[gr]=My First B4J Linux app on Greek
Depends: default-jre, openjfx
Maintainer: My Name <my[email protected]>
Description: myb4japp Application
A simple Java application using JavaFX.

------
* Depends: remove if no dependencies like for B4J with built-in Java + JavaFX files.
* Note the first space " " in the latest line: " A simple Java application using JavaFX."

▎Step 5: Create a postinstall file (optional)

If you need to perform some actions after installing the package (for example, add access rights), create a postinst file:

nano ~/dev/myb4japp/debian/DEBIAN/postinst

Paste the following content into it:

#!/bin/bash
set -e

# Example of adding execution rights
chmod +x /usr/bin/myb4japp.jar
exit 0

Don't forget to make the file executable:

chmod +x ~/dev/myb4japp/debian/DEBIAN/postinst

▎Step 6: Create a startup script

Create a script to start your application:

nano ~/dev/myb4japp/debian/usr/bin/myb4japp

Paste the following content into it ("jdk14" is your Java build + Javafx inside):

#!/bin/bash
export JAVA_HOME="$APPDIR/usr/lib/myb4japp/jdk14/"
export PATH="$JAVA_HOME/bin:$PATH"
export APP_DIR="$APPDIR/usr/lib/myb4japp" # Assuming your app-specific libraries are here

/usr/lib/myb4japp/jdk14/bin/java -jar --module-path /usr/lib/myb4japp/jdk14/javafx/lib --add-modules ALL-MODULE-PATH -Djdk.gtk.version=2 --add-opens javafx.controls/com.sun.javafx.scene.control.inputmap=ALL-UNNAMED --add-opens javafx.graphics/com.sun.javafx.scene=ALL-UNNAMED --add-opens javafx.controls/com.sun.javafx.scene.control.behavior=ALL-UNNAMED --add-opens javafx.graphics/com.sun.javafx.scene.traversal=ALL-UNNAMED --add-opens javafx.graphics/javafx.scene=ALL-UNNAMED --add-opens javafx.base/com.sun.javafx.collections=ALL-UNNAMED --add-opens javafx.controls/com.sun.javafx.scene.control=ALL-UNNAMED --add-opens javafx.base/com.sun.javafx.event=ALL-UNNAMED --add-opens javafx.graphics/com.sun.javafx.css=ALL-UNNAMED --add-opens javafx.base/com.sun.javafx.runtime=ALL-UNNAMED --add-opens javafx.controls/com.sun.javafx.scene.control.skin=ALL-UNNAMED --add-opens javafx.graphics/com.sun.javafx.geom=ALL-UNNAMED --add-opens javafx.graphics/javafx.scene.canvas=ALL-UNNAMED --add-modules=javafx.swing,javafx.media,javafx.swt,javafx.base,javafx.web,javafx.graphics,javafx.fxml,javafx.controls /usr/bin/myb4japp.jar

Make it executable:

chmod +x ~/dev/myb4japp/debian/usr/bin/myb4japp
------------------
To add an icon and create a .desktop file for your Java applications in DEB format, follow these steps:

▎Step 6: Adding an icon

1. Create a folder for the icon:
In your project, create a directory usr/share/icons/hicolor/243x243/apps/ (or another size, depending on your needs):

mkdir -p ~/dev/myb4japp/debian/usr/share/icons/hicolor/243x243/apps/

2. Copy the icon:
Place your icon (for example, myb4japp.png) in this directory:

cp /path/to/your/icon/myb4japp.png ~/dev/myb4japp/debian/usr/share/icons/hicolor/243x243/apps/

▎Step 7: Creating a .desktop file

1. Create a file myb4japp.desktop in the debian directory:

nano ~/dev/myb4japp/debian/myb4japp.desktop

2. Add content:
Paste the following content, changing the paths and names to your own:

[Desktop Entry]
Type=Application
Name=myb4japp
Version=0.994
Icon=myb4japp
Terminal=false
Categories=Education;
Exec=/usr/lib/myb4japp/jdk14/bin/java -jar --module-path /usr/lib/myb4japp/jdk14/javafx/lib --add-modules ALL-MODULE-PATH -Djdk.gtk.version=2 --add-opens javafx.controls/com.sun.javafx.scene.control.inputmap=ALL-UNNAMED --add-opens javafx.graphics/com.sun.javafx.scene=ALL-UNNAMED --add-opens javafx.controls/com.sun.javafx.scene.control.behavior=ALL-UNNAMED --add-opens javafx.graphics/com.sun.javafx.scene.traversal=ALL-UNNAMED --add-opens javafx.graphics/javafx.scene=ALL-UNNAMED --add-opens javafx.base/com.sun.javafx.collections=ALL-UNNAMED --add-opens javafx.controls/com.sun.javafx.scene.control=ALL-UNNAMED --add-opens javafx.base/com.sun.javafx.event=ALL-UNNAMED --add-opens javafx.graphics/com.sun.javafx.css=ALL-UNNAMED --add-opens javafx.base/com.sun.javafx.runtime=ALL-UNNAMED --add-opens javafx.controls/com.sun.javafx.scene.control.skin=ALL-UNNAMED --add-opens javafx.graphics/com.sun.javafx.geom=ALL-UNNAMED --add-opens javafx.graphics/javafx.scene.canvas=ALL-UNNAMED --add-modules=javafx.swing,javafx.media,javafx.swt,javafx.base,javafx.web,javafx.graphics,javafx.fxml,javafx.controls /usr/bin/myb4japp.jar

• Exec: Specify the path to your JAR file.

• Icon: Specify the name of your icon without the extension (e.g. myb4japp).

• Categories: Add categories that are relevant to your application.

▎Step 8: Installing the .desktop file

1. Create a folder for .desktop files:

mkdir -p ~/dev/myb4japp/debian/usr/share/applications/

2. Move your .desktop file to this folder:

cp ~/dev/myb4japp/debian/myb4japp.desktop ~/dev/myb4japp/debian/usr/share/applications/

▎Step 9: Update the control file

Don't forget to update the control file if needed. For example, you can add Java dependency information if required.

-------------------

▎Step 10: Build the DEB package

You are now ready to build your DEB package. Go to the parent directory and run the command:

cd ~/dev/myb4japp/debian
dpkg-deb --build . ../myb4japp_0.994_all.deb

▎Step 11: Install the package

Now you can install the created package with the following command:

sudo dpkg -i ~/dev/myb4japp/myb4japp_0.994_all.deb

▎Step 12: Run the application

Now you can run your application by running the command:

myb4japp

Very important part is the starting line of the GUI app under Linux, here the correct starting line after the DEB-package is already installed:

/usr/lib/myb4japp/jdk14/bin/java -jar --module-path /usr/lib/myb4japp/jdk14/javafx/lib --add-modules ALL-MODULE-PATH -Djdk.gtk.version=2 --add-opens javafx.controls/com.sun.javafx.scene.control.skin=ALL-UNNAMED /usr/bin/myb4japp.jar
But more correctly to include into the starting line all the modules that are generated into "release_java_modules.txt" file by building the standalone package (it depends on the libs of your project) on the target platform Linux via B4J-bridge, like:

[Desktop Entry]
Type=Application
Name=myb4japp
Version=0.994
Icon=myb4japp
Terminal=false
Categories=Education;
Exec=/usr/lib/myb4japp/jdk14/bin/java -jar --module-path /usr/lib/myb4japp/jdk14/javafx/lib --add-modules ALL-MODULE-PATH -Djdk.gtk.version=2 --add-opens javafx.controls/com.sun.javafx.scene.control.inputmap=ALL-UNNAMED --add-opens javafx.graphics/com.sun.javafx.scene=ALL-UNNAMED --add-opens javafx.controls/com.sun.javafx.scene.control.behavior=ALL-UNNAMED --add-opens javafx.graphics/com.sun.javafx.scene.traversal=ALL-UNNAMED --add-opens javafx.graphics/javafx.scene=ALL-UNNAMED --add-opens javafx.base/com.sun.javafx.collections=ALL-UNNAMED --add-opens javafx.controls/com.sun.javafx.scene.control=ALL-UNNAMED --add-opens javafx.base/com.sun.javafx.event=ALL-UNNAMED --add-opens javafx.graphics/com.sun.javafx.css=ALL-UNNAMED --add-opens javafx.base/com.sun.javafx.runtime=ALL-UNNAMED --add-opens javafx.controls/com.sun.javafx.scene.control.skin=ALL-UNNAMED --add-opens javafx.graphics/com.sun.javafx.geom=ALL-UNNAMED --add-opens javafx.graphics/javafx.scene.canvas=ALL-UNNAMED --add-modules=javafx.swing,javafx.media,javafx.swt,javafx.base,javafx.web,javafx.graphics,javafx.fxml,javafx.controls /usr/bin/myb4japp.jar



----------BONUS:------------
If to modify steps 3, 4, 6, 7 - it's possible to use default Java (downloading during .deb installation) with only distributed needed for you JavaFX.

3. Use as lib only javafx folder
4. Use "Depends: default-jre"
6-7. Use short simpler starting line with just "java" and path to the javafx:

java -jar --module-path /usr/lib/myb4japp/javafx/lib --add-modules ALL-MODULE-PATH -Djdk.gtk.version=2 --add-opens javafx.controls/com.sun.javafx.scene.control.inputmap=ALL-UNNAMED --add-opens javafx.graphics/com.sun.javafx.scene=ALL-UNNAMED --add-opens javafx.controls/com.sun.javafx.scene.control.behavior=ALL-UNNAMED --add-opens javafx.graphics/com.sun.javafx.scene.traversal=ALL-UNNAMED --add-opens javafx.graphics/javafx.scene=ALL-UNNAMED --add-opens javafx.base/com.sun.javafx.collections=ALL-UNNAMED --add-opens javafx.controls/com.sun.javafx.scene.control=ALL-UNNAMED --add-opens javafx.base/com.sun.javafx.event=ALL-UNNAMED --add-opens javafx.graphics/com.sun.javafx.css=ALL-UNNAMED --add-opens javafx.base/com.sun.javafx.runtime=ALL-UNNAMED --add-opens javafx.controls/com.sun.javafx.scene.control.skin=ALL-UNNAMED --add-opens javafx.graphics/com.sun.javafx.geom=ALL-UNNAMED --add-opens javafx.graphics/javafx.scene.canvas=ALL-UNNAMED --add-modules=javafx.swing,javafx.media,javafx.swt,javafx.base,javafx.web,javafx.graphics,javafx.fxml,javafx.controls /usr/bin/myb4japp.jar
But this variant must be installed by apt command as dpkg command - cannot resolve the dependencies:

sudo apt-get install myb4japp.deb
And the .deb file is 3 times smaller without built-in Java for Linux.
 

Attachments

  • TempDownload.png
    TempDownload.png
    10.5 KB · Views: 338
Last edited:

Theera

Expert
Licensed User
Longtime User
I use jdk 19.0.2 , I can do as the same, or not?
 

peacemaker

Expert
Licensed User
Longtime User
Depends on JavaFX version, as i know the GTK2 driver was removed from JavaFX 18, so setting "-Djdk.gtk.version=2" will not help to avoid critical error.
JavaFX 17 maybe OK, it needs to test.
 
Last edited:

peacemaker

Expert
Licensed User
Longtime User
JavaFX 17 maybe OK
I have tested:

1) with OpenJDK23 + JavaFX23 messages:

WARNING: A command line option tried to select the GTK 2 library, which was removed from JavaFX.
WARNING: The GTK 3 library will be used instead.
(java:18590): Gtk-CRITICAL **: 22:44:11.478: gtk_window_resize: assertion 'height > 0' failed

2) with OpenJDK23 + JavaFX17 is just:

(java:28299): Gtk-CRITICAL **: 22:47:10.416: IA__gtk_window_resize: assertion 'height > 0' failed

But at both variants - app works OK, no crash !
Just at WSL2 of Win10 the sizable windows are not resizable, at separate Linux Mint21 - all is OK as usual, sizeable.
 
Last edited:

peacemaker

Expert
Licensed User
Longtime User
DEB-package for Linux of B4J GIU app can be developed by using ... default Java and any needed for you JavaFX !
I have walked this way with creating .DEB-package with default JRE (v11 for Mint 21) + distributing inside .deb JavaFX17 - and can confirm that B4J app under Linux can work OK.
 
Last edited:

Theera

Expert
Licensed User
Longtime User
DEB-package for Linux of B4J GIU app can be developed by using ... default Java and any needed for you JavaFX !
I have walked this way and confirm that B4J app under Linux can work OK.
Many thanks for your replies.
 

peacemaker

Expert
Licensed User
Longtime User
Сreating a `.deb` package for a server-side non-GUI Java application, using `default-jre-headless` as a dependency and including a systemd service file.

## 📁 Preparing the Environment and Package Structure

Install the necessary build tools:

Bash:
sudo apt update
sudo apt install build-essential devscripts debhelper

Create a working directory for your package. Let's name the application `myapp`:

Bash:
mkdir -p ~/dev/myapp/debian
cd ~/dev/myapp

We will build the package structure inside the `debian/` directory. It will be simpler than for a GUI app, but we will add a folder for systemd.

## 📄 Creating the Directory Structure

Create the following directories inside `~/dev/myapp/debian`:

Bash:
mkdir -p debian/DEBIAN
mkdir -p debian/usr/bin
mkdir -p debian/usr/share/myapp
mkdir -p debian/lib/systemd/system

* `DEBIAN/` — for package metadata and scripts.
* `usr/bin/` — for the executable launch script.
* `usr/share/myapp/` — for your application's JAR file.
* `lib/systemd/system/` — for the systemd service file.

## ⚙️ Metadata File: `control`

Create the file `~/dev/myapp/debian/DEBIAN/control`:

Bash:
nano debian/DEBIAN/control

Add the following content. Pay attention to the `Depends` field — it specifies the need for a JRE without graphics (`headless`), which is optimal for servers.

INI:
Package: myapp
Version: 1.0.0
Section: utils
Priority: optional
Architecture: all
Depends: default-jre-headless (>= 11)
Maintainer: Your Name <[email protected]>
Description: My server-side Java application
 A non-GUI Java application that runs as a system service.
 It processes data and provides API endpoints.

* `default-jre-headless` is a meta-package that always points to the current version of the OpenJDK JRE (without X11) used in the distribution. This guarantees that when your package is installed, the necessary runtime environment will be installed as well.

## 📦 Placing Application Files

1. **Copy your JAR file** to the target directory:
Bash:
    cp ~/path/to/your/compiled-app.jar debian/usr/share/myapp/myapp.jar
Replace `~/path/to/your/compiled-app.jar` with the actual path.

2. **Create an executable script** for convenient command-line launching (while the service is the main focus, this is useful for debugging):
Bash:
    nano debian/usr/bin/myapp
Script content:
Bash:
    #!/bin/sh
    # Using exec is important for correct signal handling by systemd
    exec java -jar /usr/share/myapp/myapp.jar "$@"
* This script simply calls the JAR with the system Java.
* The `exec` flag replaces the shell process with the Java process, which is crucial for proper signal handling from systemd.

3. **Make the script executable**:
Bash:
    chmod +x debian/usr/bin/myapp

## 🖥️ Creating the systemd Unit File

This is the key difference for a server application. Create the service file `~/dev/myapp/debian/lib/systemd/system/myapp.service`:

Bash:
nano debian/lib/systemd/system/myapp.service

Add the following content (adapt it to your needs):

INI:
[Unit]
Description=My Java Server Application
After=network.target syslog.target
Wants=network.target

[Service]
Type=simple
User=myapp
Group=myapp

# Successful exit code for SIGTERM (143 = 128 + 15)
SuccessExitStatus=143

WorkingDirectory=/usr/share/myapp
Environment="JAVA_HOME=/usr/lib/jvm/default-java"
ExecStart=/usr/bin/myapp
ExecStop=/bin/kill -15 $MAINPID
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

* `After=network.target` — ensures the network is available before startup.
* `Type=simple` — suitable as the process runs directly.
* `User/Group` — **highly recommended** to create a dedicated system user to run the application (not as root). We will add its creation later.
* `SuccessExitStatus=143` — tells systemd that exit code 143 (SIGTERM) is a normal termination, not an error.
* `ExecStart` — points to our script in `/usr/bin`.
* `Restart=on-failure` — automatically restart the application in case of a crash.

## ⚙️ `postinst` and `prerm` Scripts

For systemd to work correctly and to create a user, we need scripts that run during package installation and removal.

1. **Create `postinst` (actions after installation)**:
Bash:
    nano debian/DEBIAN/postinst
Content:
Bash:
    #!/bin/sh
    set -e

    # Create system user if it doesn't exist
    if ! getent passwd myapp > /dev/null; then
        adduser --system --group --no-create-home --home /usr/share/myapp myapp
    fi

    # Set ownership of the application directory
    chown -R myapp:myapp /usr/share/myapp

    # Reload systemd so it knows about the new unit
    systemctl daemon-reload || true

    # Automatic service start (recommended, but can be left for the admin to decide)
    # systemctl enable myapp.service || true
    # systemctl start myapp.service || true

    # Display information for the user
    echo "---------------------------------------------------------------------"
    echo "myapp service installed."
    echo "To start it manually: sudo systemctl start myapp"
    echo "To enable auto-start: sudo systemctl enable myapp"
    echo "---------------------------------------------------------------------"

    exit 0
* The script creates a system user `myapp`, which improves security.
* It performs `daemon-reload` so systemd sees the new or updated unit file.
* In this example, we do not start the service automatically, but instead give instructions to the administrator. This is a safer approach.

2. **Create `prerm` (actions before removal)**:
Bash:
    nano debian/DEBIAN/prerm
Content:
Bash:
    #!/bin/sh
    set -e

    # Stop the service when the package is removed
    if [ -f /lib/systemd/system/myapp.service ]; then
        systemctl stop myapp.service || true
        systemctl disable myapp.service || true
    fi

    exit 0

3. **Make both scripts executable**:
Bash:
    chmod +x debian/DEBIAN/postinst debian/DEBIAN/prerm

## 📦 Building the Package

Go to the parent directory (`~/dev/myapp`) and run the build command:

Bash:
cd ~/dev/myapp
dpkg-deb --build debian .

After execution, the file `myapp_1.0.0_all.deb` will appear in the current directory.

Check the package contents:
Bash:
dpkg --contents myapp_1.0.0_all.deb
And its metadata:
Bash:
dpkg --info myapp_1.0.0_all.deb

## 🚀 Installation and Verification

1. **Install the package** (root privileges required):
Bash:
    sudo dpkg -i myapp_1.0.0_all.deb
If there are errors about missing dependencies (e.g., `default-jre-headless` not installed), run:
Bash:
    sudo apt-get install -f
This will install any required dependencies.

2. **Check the service status**:
Bash:
    sudo systemctl status myapp.service
(It should be inactive at this point, as we didn't start it automatically).

3. **Start the service**:
Bash:
    sudo systemctl start myapp.service
    sudo systemctl status myapp.service

4. **Enable auto-start** (if needed):
Bash:
    sudo systemctl enable myapp.service

## 📝 Final Package Structure

For clarity, the complete structure of your build directory:

~/dev/myapp/
├── debian/
│ ├── DEBIAN/
│ │ ├── control
│ │ ├── postinst
│ │ └── prerm
│ ├── lib/
│ │ └── systemd/
│ │ └── system/
│ │ └── myapp.service
│ ├── usr/
│ │ ├── bin/
│ │ │ └── myapp
│ │ └── share/
│ │ └── myapp/
│ │ └── myapp.jar
│ └── (other directories if needed)
└── myapp_1.0.0_all.deb (built package)
 
Last edited:
Top