Java-GI is a tool for generating GObject-Introspection bindings for Java. The generated bindings use the Panama Foreign Function & Memory API (JEP 434, currently in preview status) to directly access native resources from inside the JVM, with wrapper classes based on GObject-Introspection to offer an elegant API. Java-GI version 0.6.1 generates bindings to develop Java applications with the following libraries:
Library | Version |
---|---|
GLib | 2.76 |
GTK | 4.10 |
LibAdwaita | 1.3 |
GStreamer | 1.20 |
GtkSourceview | 5.9 |
WebkitGtk | 2.41 |
Please note that Java-GI is still under active development. The bindings should not be used in a production environment yet, and the API is subject to unannounced changes. However, feel free to try out the latest release; feedback is welcome.
For more information, visit the Java-GI website.
-
To use Java-GI in your app, you need to specify a few things in your
gradle.build
file, as described on here. Furthermore, you must set the Java language version to 20, and, while the Panama foreign function API is still in preview status, set the--enable-preview
option to the compile and execution tasks. See thisbuild.gradle
file for a complete example. -
Write, compile and run a GTK application:
import org.gnome.gtk.*;
import org.gnome.gio.ApplicationFlags;
public class HelloWorld {
public static void main(String[] args) {
new HelloWorld(args);
}
private final Application app;
public HelloWorld(String[] args) {
app = new Application("my.example.HelloApp", ApplicationFlags.DEFAULT_FLAGS);
app.onActivate(this::activate);
app.run(args);
}
public void activate() {
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.newWithLabel("Hello world!");
button.onClicked(window::close);
box.append(button);
window.setChild(box);
window.present();
}
}
The result:
You can find some examples here. Each example can be separately built and run with gradle run
:
![]() |
![]() |
![]() |
![]() |
---|---|---|---|
Hello World | Peg Solitaire | Calculator | Notepad |
Nearly all types, functions and parameters defined in the GIR files are supported by Java-GI. Even complex function signatures with combinations of arrays, callbacks, out-parameters and varargs are available in Java.
Some interesting features of the bindings that Java-GI generates:
All API docstrings are translated into Javadoc, so they are directly available in your IDE.
As an example, the generated documentation of gtk_button_get_icon_name
contains links to other methods, and specifies the return value. This is all translated to valid Javadoc:
/**
* Returns the icon name of the button.
* <p>
* If the icon name has not been set with {@link Button#setIconName}
* the return value will be {@code null}. This will be the case if you create
* an empty button with {@link Button#Button} to use as a container.
* @return The icon name set via {@link Button#setIconName}
*/
public @Nullable java.lang.String getIconName() {
...
The Javadoc is also published online:
GObject classes are available as Java classes (obviously). The GObject TypeClass definition is an inner class in the Java class.
Interfaces are mapped to Java interfaces, using default
interface methods to call native methods.
Type aliases (typedef
s in C) for classes, records and interfaces are represented in Java with a subclass of the original type. Aliases for primitive types such as int
or float
are represented by simple wrapper classes.
Enumeration types are represented as Java enum
types.
Most classes have one or more constructors. However, constructors in GTK are often overloaded, and the name contains valuable information for the user. Java-GI therefore maps constructors named "new" to regular Java constructors, and generates static factory methods for all other constructors:
// gtk_button_new
var button1 = new Button();
// gtk_button_new_with_label
var button2 = Button.newWithLabel("Open...");
// gtk_button_new_from_icon_name
var button3 = Button.newFromIconName("document-open");
Some struct types (called "records" in GObject-Introspection) don't have constructors, because in C these are meant to be stack-allocated. An example is Gdk.RGBA
. Java-GI offers a static allocate
method that will allocate a new struct that you can use. You can either allocate an empty struct (var color = RGBA.allocate();
) and fill in the values later, or pass the values immediately: var purple = RGBA.allocate(0.9f, 0.1f, 0.9f, 1.0f);
Memory management of native resources is automatically taken care of. Java-GI uses GObject toggle references to dispose the native object when the Java instance is garbage-collected, and releases all other memory allocations (for strings, arrays and structs) after use.
Signals are mapped to type-safe methods and objects in Java. (Detailed signals like notify
have an extra String
parameter.) A signal can be connected to a lambda expression or method reference:
var button = Button.newWithLabel("Close");
button.onClicked(window::close);
For every signal, a method to connect (e.g. onClicked
) and emit the signal (emitClicked
) is included in the API. New signal connections return a Signal
object, that allows you to disconnect, block and unblock a signal, or check whether the signal is still connected.
Functions with callback parameters are supported too. The generated Java bindings contain @FunctionalInterface
definitions for all callback functions to ensure type safety.
Closures are marshaled to Java methods using reflection.
You can easily register a Java class as a GType:
public class MyWidget extends Widget {
public static Type gtype = Types.register(MyWidget.class);
// Construct new instance
public MyWidget newInstance() {
return GObject.newInstance(gtype);
}
// Default constructor, used by Java-GI for marshaling
public MyWidget(Addressable address) {
super(address);
}
}
You can define custom GObject Properties with an annotation:
@Property(name="my-number", type=ParamSpecInt.class)
public int getMyNumber() {
return ...;
}
@Property(name="my-number") {
public void setMyNumber(int number) {
...
}
Java classes can implement interfaces and override methods without any additional effort. You can override any method you want; however, when you override methods from an interface (or virtual methods from a parent class), Java-GI will register it in the GObject type system, so native code will call your Java method too. An example implementation of the ListModel
interface is included in the GLib module.
Read the documentation for an overview of all the possibilities.
A class with a @GtkTemplate
annotation will be registered as a Gtk composite template class:
@GtkTemplate(name="HelloWindow", ui="/my/example/hello-window.ui")
public class HelloWindow extends ApplicationWindow {
private static Type gtype = Types.register(HelloWindow.class);
@GtkChild(name="header_bar")
public HeaderBar header;
@GtkChild
public Label label;
@GtkCallback
public void buttonClicked() {
...
}
...
In the above example, the header
and label
fields and the buttonClicked
callback function are all declared the hello-window.ui
file.
You can read more about template classes in the documentation.
Java-GI takes care of marshaling Java values from and to native values. When working with arrays, Java-GI will automatically copy native array contents from and to a Java array, marshaling the contents to the correct types along the way. A null
terminator is added where applicable. You also don't need to specify the array length as a separate parameter.
Nullability of parameters (as defined in the GObject-introspection attributes) is indicated with @Nullable
and @NotNull
attributes, and checked at runtime. The nullability attributes are imported from Jetbrains Annotations (as a compile-time-only dependency).
Variadic functions (varargs) are supported too:
Dialog d = Dialog.newWithButtons(
"Test dialog",
window,
DialogFlags.MODAL,
"Accept",
ResponseType.ACCEPT,
"Cancel",
ResponseType.CANCEL,
null
);
d.show();
Out-parameters are mapped to a simple Out<T>
container-type in Java, that offers typesafe get()
and set()
methods to retrieve or modify the value.
File file = ...
Out<byte[]> contents = new Out<byte[]>();
file.loadContents(null, contents, null));
System.out.printf("Read %d bytes\n", contents.get().length);
You can construct an object with properties using a Builder pattern. In the "Hello World" app above, it's used to create a Box
. It can be used for any other type too:
var window = ApplicationWindow.builder()
.setApplication(this)
.setTitle("Window")
.setDefaultWidth(300)
.setDefaultHeight(200)
.build();
Java-GI generates builders for all classes. In a builder, you can set the properties of the class, its parents, and all implemented interfaces.
GError
parameters are mapped to Java GErrorException
s.
try {
file.replaceContents(contents, null, false, FileCreateFlags.NONE, null, null);
} catch (GErrorException e) {
e.printStackTrace();
}
The Java-GI bindings are cross-platform: You can use the same jar on all supported operating systems (Linux, Windows and MacOS) provided that the native libraries are installed. Platform-specific types and methods (like Gtk.PrintUnixDialog
) check the operating system at runtime and throw an UnsupportedPlatformException
when neccessary.
To build Java-GI for yourself, make changes, or use Java-GI to generate bindings for other (GObject-Introspection based) libraries, follow the instructions here.
The bindings are still under active development and have not been thoroughly tested yet. The most notable issues and missing features are currently:
- Java does not distinguish between signed and unsigned data types. Be extra careful when native code returns, for example, a
guint
. - There are still a few memory leaks.
- Some functions (like
Gio.DesktopAppInfo.search
) work with nested arrays (gchar***
). These aren't supported yet.