What I learned at JSpirit—Graal

Last weekend, I joined the amazing JSpirit unconference. What a venue—a working distillery! We discussed Java, surrounded by barrels and aromatic (and presumably slightly alcoholic) vapors. Here is what I learned about Graal.

Why Graal?


A great strength of Java is the virtual machine. The just-in-time compiler monitors the running program and optimizes the “hot spots” that actually execute a lot. Because the VM knows about all classes that have been loaded, it can make optimizations that a regular ahead-of-time compiler cannot make. For example, if a method is never overridden (such as 99% of your boring getFoo methods), the body of the method can be inlined. In the unlikely event that a class is loaded that does override the method, the inlining can be undone.

But the just-in-time compiling increase startup time. The JIT needs to “warm up” and run some code in slow, interpreted mode, before it can figure out what to compile into machine code. When an app server runs for months on end, the warm up doesn't matter, but it's not so great when a Java program only runs for a brief time, as is common these days with “serverless” functions.

That's where Graal comes it. It can be used to call between Java and other programming languages—hence the holy grail reference. But I think most people care more about the fact that Graal can also produce native code. Of course, a native executable starts quickly. The biggest limitation: no dynamic class loading. You couldn't produce an app server with Graal, but in these days of microservices, who cares.


You can download Graal from graalvm.org, but the installation directions for the free “community edition” are less than stellar. this blog helped me out.

On my Linux machine, I downloaded the graalvm-ce-java11-linux-amd64-version.tar.gz fine from the Github release page. Mac and Windows releases are on the same page.

Uncompress somewhere—the home directory is fine for getting started. Then run

path/to/graalvm/bin/gu install native-image

Now put a Hello, World program somewhere and run

path/to/graalvm/bin/javac HelloWorldApp.java
path/to/graalvm/bin/native-image HelloWorldApp

You will notice that the second step takes a good long time. The result is an executable file helloworldapp. You can run it as


It starts instantly 😁

Also, it's 6.7MB in size.

Is That Big?

The Hello, World class file is 255 bytes. Of course, that doesn't really count. When it runs, there is a whole virtual machine. And the shared library libjvm.so is 21.7MB.

What about other languages? I tried the Hello, 世界 program in Go. It came in at 2MB. I checked with readelf that the executable was statically linked. Apparently, Go programmers are shocked, shocked. But of course, Go has a nontrivial execution environment that provides services such as garbage collection and coroutines, so that is only to be expected.

And Rust did no better, with the “Hello, World!” program at 2.5MB.

With C, statically linked, it was 825KB.

So, I suppose 6.7MB is bigger, but not an order of magnitude bigger than Go and Rust. Presumably the Graal Java runtime works harder.

Graal Limitations

When I first heard about Graal, someone said that it doesn't handle reflection. I didn't quite understand. When an object is in memory, surely one could instrument it with sufficient information to make reflection work. Maybe not for every object, but perhaps one could specify at compile-time which classes, methods, or fields should be so instrumented?

And that's exactly how it works.

Here are instructions for configuring the native compiler. I tried it out with the following example:

import java.time.*;
import java.beans.*;
import java.lang.reflect.*;

public class BeanTest
   public static void main(String[] args)
      throws ReflectiveOperationException, IntrospectionException
      LocalTime now = LocalTime.now();
      BeanInfo info = Introspector.getBeanInfo(LocalTime.class, Object.class);
      for (PropertyDescriptor pd : info.getPropertyDescriptors())
         Method getter = pd.getReadMethod();
         System.out.println("Property " + pd.getName() + " has value " + getter.invoke(now));

When you run the program with java BeanTest, you get an output such as

Property hour has value 18
Property minute has value 40
Property nano has value 658866000
Property second has value 15

Of course, the Introspector.getBeanInfo method must enumerate all public methods of the given class to find out which method names start with get.

When compiling and running natively, I was surprised to get no output at all. From reading through this article, I expected an exception.

Then I made a file reflect-config.json with this content:

    "name" : "java.time.LocalTime",
    "allDeclaredConstructors" : true,
    "allPublicConstructors" : true,
    "allDeclaredMethods" : true,
    "allPublicMethods" : true,
    "allDeclaredClasses" : true,
    "allPublicClasses" : true

and compiled with

/path/to/graalvm/bin/native-image -H:ReflectionConfigurationFiles=reflect-config.json BeanTest

Now the resulting executable worked correctly.

The Graal docs advertise a tracing agent that can produce this configuration file automatically. Unfortunately, that didn't work for me—I got an empty JSON file for reflection.

So, that's what a project such as Quarkus needs to do—figure out where reflection is needed and instruct the native compiler.

The native beantest, by the way, was 9.6MB.

In this list of limitations, you can see that lots of features are supported. But serialization is not one of them. It ominously says: “Java serialization is currently not supported with native image. Thus classes implementing java.io.Serializable or using java serialization primitives eg : Object[Input|Output]Stream.[read|write]Object will report an error”

I didn't understand this because obviously classes such as LocalTime or String implement Serializable. And I don't think the wording is right. I was able to run the following program:

public class SerializableTest {
   public static void main(String[] args) {
      Person fred = new Person("Fred");
      System.out.println("Hello, " + fred.getName());

class Person implements java.io.Serializable {
   private String name;
   public Person(String name) {
      this.name = name;

   public String getName() {
      return name;

Of course, when you try to serialize the object,

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("fred.ser"));

then you get an exception:

Exception in thread "main" com.oracle.svm.core.jdk.UnsupportedFeatureError: ObjectOutputStream.writeObject()
	at com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:101)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:68)
	at SerializableTest.main(SerializableTest.java:7)


This appears to be a seriously cool technology. Why should I mess with a toy language 😁 when I can actually use Java—ok, most of Java. I'll have to spend more time understanding it all, and start applying it to real projects.

The other cool thing, to me, is the unconference format. I went to JSpirit and boldly put up a sticky tag saying “teach me how Graal works”. There was no one expert, but of the people who came, more than half knew something, and collectively they knew a lot, and I learned a lot.—“It is not the answer that enlightens, but the question.”

Comments powered by Talkyard.