Skip to content

Basic usage

Required Java version

To use Java-GI, download and install OpenJDK version 25 or newer. Java-GI uses many features of modern JDK versions, such as the Foreign Function & Memory API (JEP-454), Markdown Javadoc (JEP-467) and Flexible Constructor Bodies (JEP-513), so any JDK version below 25 will not work.

Java-GI does not work with Graal Native Image, for two reasons:

  • GraalVM requires registration of all foreign calls at compile time, but this has not (yet) been implemented in the bindings generator.

  • Java-GI uses runtime reflection for GType registration, GClosure marshaling, and other features. This would have to be completely replaced by a new solution based on annotation processors and a ServiceLoader.

Dependencies

Make sure that the native GLib, Gtk and/or GStreamer libraries are installed on your operating system.

Next, add the dependencies. For example, to add Gtk as a dependency:

<dependency>
  <groupId>org.java-gi</groupId>
  <artifactId>gtk</artifactId>
  <version>1.0.0-RC1</version>
</dependency>
repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.java-gi:gtk:1.0.0-RC1'
}
repositories {
    mavenCentral()
}

dependencies {
    implementation("org.java-gi:gtk:1.0.0-RC1")
}
libraryDependencies += "org.java-gi" % "gtk" % "1.0.0-RC1"
[org.java-gi/gtk "1.0.0-RC1"]
{:deps {
  org.java-gi/gtk {:mvn/version "1.0.0-RC1"}
}}
repositories = List.of(MAVEN_CENTRAL);
scope(main)
    .include(dependency("org.java-gi:gtk:1.0.0-RC1"));

This will add the Gtk bindings to the application's compile and runtime classpath. Other libraries, like webkit, gstreamer, adw and gtksourceview can be included likewise. The complete list of available libraries is available here.

Application code

An example Gtk application with a "Hello world" button can be created as follows:

package my.example.helloapp;

import org.gnome.gtk.*;
import org.gnome.gio.ApplicationFlags;

public class HelloWorld {

    public static void main(String[] args) {
        new HelloWorld(args);
    }

    public HelloWorld(String[] args) {
        var app = new Application("my.example.HelloApp");
        app.onActivate(() -> activate(app));
        app.run(args);
    }

    public void activate(Application app) {
        var window = new ApplicationWindow(app);
        window.setTitle("GTK from Java");
        window.setDefaultSize(300, 200);

        var box = Box.builder()
            .setOrientation(Orientation.VERTICAL)
            .setHalign(Align.CENTER)
            .setValign(Align.CENTER)
            .build();

        var button = Button.withLabel("Hello world!");
        button.onClicked(window::close);

        box.append(button);
        window.setChild(box);
        window.present();
    }
}
package my.example.helloapp

import org.gnome.gio.ApplicationFlags
import org.gnome.gtk.*

fun main(args: Array<String>) {
    val app = Application("my.example.HelloApp")
    app.onActivate { activate(app) }
    app.run(args)
}

private fun activate(app: Application) {
    val window = ApplicationWindow(app)
    window.title = "GTK from Kotlin"
    window.setDefaultSize(300, 200)

    val box = Box.builder()
        .setOrientation(Orientation.VERTICAL)
        .setHalign(Align.CENTER)
        .setValign(Align.CENTER)
        .build()

    val button = Button.withLabel("Hello world!")
    button.onClicked(window::close)

    box.append(button)
    window.child = box
    window.present()
}
package my.example.helloapp

import org.gnome.gtk.*
import org.gnome.gio.ApplicationFlags

class HelloWorld {
    def activate(app: Application) = {
        var window = new ApplicationWindow(app)
        window.setTitle("GTK from Scala")

        var box = new Box(Orientation.VERTICAL, 1) {
            setHalign(Align.CENTER)
            setValign(Align.CENTER)
        }

        var button = Button.withLabel("Hello world!")
        button.onClicked{() => window.close}

        box.append(button)
        window.setChild(box)
        window.present()
    }
}

object HelloWorld {
    def main(args: Array[String]) = {
        val app = Application("my.example.HelloApp")
        app.onActivate(() => HelloWorld().activate(app))
        app.run(args);
        ()
    }
}
(ns hello
  (:import
   (org.gnome.gio ApplicationFlags Application$ActivateCallback)
   (org.gnome.gtk Align Application ApplicationWindow Box Button Button$ClickedCallback Orientation)))

(def app (doto (Application. "org.javagi.examples.HelloWorldClojure" (into-array ApplicationFlags [ApplicationFlags/DEFAULT_FLAGS]))
           (.onActivate (reify Application$ActivateCallback
                          (run [this]
                            (let [app-window (ApplicationWindow. app)]
                              (doto app-window
                                (.setTitle "GTK from Clojure")
                                (.setDefaultSize 300 200)
                                (.setChild (doto (Box. Orientation/VERTICAL 0)
                                             (.setHalign Align/CENTER)
                                             (.setValign Align/CENTER)
                                             (.append (doto (Button/withLabel "Hello world!")
                                                        (.onClicked (reify Button$ClickedCallback
                                                                      (run [this] (.close app-window))))))))
                                (.present))))))))

(defn run [opts]
  (.run app nil))

Compile and run

Build and run the application using your IDE or build tool of choice. The following command-line parameters are useful:

  • Add --enable-native-access=ALL-UNNAMED to suppress warnings about native access. (This warning will become an error in a future JDK release.)

  • If you encounter an error about a missing library, override the java library path with "-Djava.library.path=/usr/lib/...".

See this Gradle build file for a typical example.

Use an IDE

An Integrated Development Environment (IDE) with support for Java is the most efficient way to develop software in Java. IDEs will help setup a project and build configuration, navigate and refactor source code, detect problems and suggest improvements. The most commonly used Java IDEs are:

If you often work from the command line, SDKMAN! will prove useful to manage your installed JDKs and build tools.

Further reading

For more advanced instructions on using Java-GI consult this page, and read about subclassing GObject classes with Java-GI here. If you're new to Gtk development, read the Gtk "Getting started" guide that has been translated to use Java for all code examples.

Platform-specific notes

Linux

On most Linux distributions, Gtk will already be installed. Java-GI will load shared libraries using dlopen, and fallback to the java.library.path. So in most cases, you can simply run your application with --enable-native-access=ALL-UNNAMED:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <version>3.6.3</version>
  <configuration>
    <mainClass>...</mainClass>
    <jvmArguments>
      <jvmArgument>--enable-native-access=ALL-UNNAMED</jvmArgument>
    </jvmArguments>
  </configuration>
</plugin>
application {
    applicationDefaultJvmArgs = ['--enable-native-access=ALL-UNNAMED']
    mainClass = ...
}
application {
    applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED")
    mainClass = ...
}
fork := true,
run / javaOptions += "--enable-native-access=ALL-UNNAMED"
:jvm-opts ["--enable-native-access=ALL-UNNAMED"]
:jvm-opts ["--enable-native-access=ALL-UNNAMED"]
runOperation()
    .javaOptions().enableNativeAccess("ALL-UNNAMED");

MacOS

On MacOS, you can install Gtk using Homebrew. Gtk needs to run on the main thread, therefore you need to set the parameter -XstartOnFirstThread. A typical build configuration will look like this:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <version>3.6.3</version>
  <configuration>
    <mainClass>...</mainClass>
    <jvmArguments>
      <jvmArgument>--enable-native-access=ALL-UNNAMED</jvmArgument>
      <jvmArgument>-Djava.library.path=/opt/homebrew/lib</jvmArgument>
      <jvmArgument>-XstartOnFirstThread</jvmArgument>
    </jvmArguments>
  </configuration>
</plugin>
application {
    applicationDefaultJvmArgs = [
        '--enable-native-access=ALL-UNNAMED',
        '-Djava.library.path=/opt/homebrew/lib',
        '-XstartOnFirstThread'
    ]
    mainClass = ...
}
application {
    applicationDefaultJvmArgs = listOf(
        "--enable-native-access=ALL-UNNAMED",
        "-Djava.library.path=/opt/homebrew/lib",
        "-XstartOnFirstThread"
    )
    mainClass = ...
}
fork := true,
run / javaOptions ++= Seq(
           "--enable-native-access=ALL-UNNAMED",
           "-Djava.library.path=/opt/homebrew/lib",
           "-XstartOnFirstThread")
:jvm-opts ["--enable-native-access=ALL-UNNAMED",
           "-Djava.library.path=/opt/homebrew/lib",
           "-XstartOnFirstThread"]
:jvm-opts ["--enable-native-access=ALL-UNNAMED",
           "-Djava.library.path=/opt/homebrew/lib",
           "-XstartOnFirstThread"]
runOperation()
    .javaOptions(List.of(
        "--enable-native-access=ALL-UNNAMED",
        "-Djava.library.path=/opt/homebrew/lib",
        "-XstartOnFirstThread"));

Windows

On Windows, Gtk can be installed with MSYS2. A typical build configuration will look like this:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <version>3.6.3</version>
  <configuration>
    <mainClass>...</mainClass>
    <jvmArguments>
      <jvmArgument>--enable-native-access=ALL-UNNAMED</jvmArgument>
      <jvmArgument>-Djava.library.path=C:/msys64/mingw64/bin</jvmArgument>
    </jvmArguments>
  </configuration>
</plugin>
application {
    applicationDefaultJvmArgs = [
        '--enable-native-access=ALL-UNNAMED',
        '-Djava.library.path=C:/msys64/mingw64/bin',
    ]
    mainClass = ...
}
application {
    applicationDefaultJvmArgs = listOf(
        "--enable-native-access=ALL-UNNAMED",
        "-Djava.library.path=C:/msys64/mingw64/bin",
    )
    mainClass = ...
}
fork := true,
run / javaOptions ++= Seq(
           "--enable-native-access=ALL-UNNAMED",
           "-Djava.library.path=C:/msys64/mingw64/bin")
:jvm-opts ["--enable-native-access=ALL-UNNAMED",
           "-Djava.library.path=C:/msys64/mingw64/bin"]
:jvm-opts ["--enable-native-access=ALL-UNNAMED",
           "-Djava.library.path=C:/msys64/mingw64/bin"]
runOperation()
    .javaOptions(List.of(
        "--enable-native-access=ALL-UNNAMED",
        "-Djava.library.path=C:/msys64/mingw64/bin"));