GraalVM native images

Quick overview


Concepts

Starting with version 4.13.0, the driver ships with embedded GraalVM configuration files that allow GraalVM native images including the driver to be built without hassle, barring a few exceptions and caveats listed below.

Classes instantiated by reflection

The driver instantiates its components by reflection. The actual classes that will be instantiated in this way need to be registered for reflection. All built-in implementations of various driver components, such as LoadBalancingPolicy or TimestampGenerator, are automatically registered for reflection, along with a few other internal components tha are also instantiated by reflection. You don’t need to manually register any of these built-in implementations.

But if you intend to use a custom implementation in lieu of a driver built-in class, then it is your responsibility to register that custom implementation for reflection.

For example, assuming that you have the following load balancing policy implementation:


package com.example.app;

import com.datastax.oss.driver.api.core.context.DriverContext;
import com.datastax.oss.driver.api.core.metadata.Node;
import com.datastax.oss.driver.internal.core.loadbalancing.DefaultLoadBalancingPolicy;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

public class CustomLoadBalancingPolicy extends DefaultLoadBalancingPolicy {

  public CustomLoadBalancingPolicy(DriverContext context, String profileName) {
    super(context, profileName);
  }
  // rest of class omitted for brevity
}

And assuming that you declared the above class in your application.conf file as follows:

datastax-java-driver.basic{
  load-balancing-policy.class = com.example.app.CustomLoadBalancingPolicy
}

Then you will have to register that class for reflection:

  1. Create the following reflection.json file, or add the entry to an existing file:
[
  { "name": "com.example.app.CustomLoadBalancingPolicy", "allPublicConstructors": true }
]
  1. When invoking the native image builder, add a -H:ReflectionConfigurationFiles=reflection.json flag and point it to the file created above.

Note: some frameworks allow you to simplify the registration process. For example, Quarkus offers the io.quarkus.runtime.annotations.RegisterForReflection annotation that you can use to annotate your class:

@RegisterForReflection
public class CustomLoadBalancingPolicy extends DefaultLoadBalancingPolicy {
  //...
}

In this case, no other manual configuration is required for the above class to be correctly registered for reflection.

Configuration resources

The default driver configuration mechanism is based on the TypeSafe Config library. TypeSafe Config looks for a few classpath resources when initializing the configuration: reference.conf, application.conf, application.json, application.properties. These classpath resources are all automatically included in the native image: you should not need to do it manually. See Accessing Resources in Native Images for more information on how classpath resources are handled in native images.

Configuring the logging backend

When configuring logging, the choice of a backend must be considered carefully, as most logging backends resort to reflection during their configuration phase.

By default, GraalVM native images provide support for the java.util.logging (JUL) backend. See this page for more information.

For other logging backends, please refer to the logging library documentation to find out if GraalVM native images are supported.

Using reactive-style programming

The reactive execution model is compatible with GraalVM native images, but the following configurations must be added:

  1. Create the following reflection.json file, or add the entry to an existing file:
[
  { "name": "org.reactivestreams.Publisher" }
]
  1. When invoking the native image builder, add a -H:ReflectionConfigurationFiles=reflection.json flag and point it to the file created above.

Using the Jackson JSON library

Jackson is used in a few places in the driver, but is an optional dependency; if you intend to use Jackson, the following configurations must be added:

  1. Create the following reflection.json file, or add these entries to an existing file:
[
  { "name": "com.fasterxml.jackson.core.JsonParser" },
  { "name": "com.fasterxml.jackson.databind.ObjectMapper" }
]

Important: when using the shaded jar – which is not officially supported on GraalVM native images, see below for more details – replace the above entries with the below ones:

[
  { "name": "com.datastax.oss.driver.shaded.fasterxml.jackson.core.JsonParser" },
  { "name": "com.datastax.oss.driver.shaded.fasterxml.jackson.databind.ObjectMapper" }
]
  1. When invoking the native image builder, add a -H:ReflectionConfigurationFiles=reflection.json flag and point it to the file created above.

Enabling compression

When using compression, only LZ4 can be enabled in native images. Snappy compression is not supported.

In order for LZ4 compression to work in a native image, the following additional GraalVM configuration is required:

  1. Create the following reflection.json file, or add these entries to an existing file:
[
  { "name" : "net.jpountz.lz4.LZ4Compressor" },
  {
    "name" : "net.jpountz.lz4.LZ4JNICompressor",
    "allDeclaredConstructors": true,
    "allPublicFields": true
  },
  {
    "name" : "net.jpountz.lz4.LZ4JavaSafeCompressor",
    "allDeclaredConstructors": true,
    "allPublicFields": true
  },
  {
    "name" : "net.jpountz.lz4.LZ4JavaUnsafeCompressor",
    "allDeclaredConstructors": true,
    "allPublicFields": true
  },
  {
    "name" : "net.jpountz.lz4.LZ4HCJavaSafeCompressor",
    "allDeclaredConstructors": true,
    "allPublicFields": true
  },
  {
    "name" : "net.jpountz.lz4.LZ4HCJavaUnsafeCompressor",
    "allDeclaredConstructors": true,
    "allPublicFields": true
  },
  {
    "name" : "net.jpountz.lz4.LZ4JavaSafeSafeDecompressor",
    "allDeclaredConstructors": true,
    "allPublicFields": true
  },
  {
    "name" : "net.jpountz.lz4.LZ4JavaSafeFastDecompressor",
    "allDeclaredConstructors": true,
    "allPublicFields": true
  },
  {
    "name" : "net.jpountz.lz4.LZ4JavaUnsafeSafeDecompressor",
    "allDeclaredConstructors": true,
    "allPublicFields": true
  },
  {
    "name" : "net.jpountz.lz4.LZ4JavaUnsafeFastDecompressor",
    "allDeclaredConstructors": true,
    "allPublicFields": true
  }
]
  1. When invoking the native image builder, add a -H:ReflectionConfigurationFiles=reflection.json flag and point it to the file created above.

Native calls

The driver performs a few native calls using JNR.

Starting with driver 4.7.0, native calls are also possible in a GraalVM native image, without any extra configuration.

Using DataStax Enterprise (DSE) features

DSE Geospatial types

DSE Geospatial types are supported on GraalVM native images; the following configurations must be added:

  1. Create the following reflection.json file, or add the entry to an existing file:
[
  { "name": "com.esri.core.geometry.ogc.OGCGeometry" }
]

Important: when using the shaded jar – which is not officially supported on GraalVM native images, as stated above – replace the above entry with the below one:

[
  { "name": "com.datastax.oss.driver.shaded.esri.core.geometry.ogc.OGCGeometry" }
]
  1. When invoking the native image builder, add a -H:ReflectionConfigurationFiles=reflection.json flag and point it to the file created above.

DSE Graph

DSE Graph is not officially supported on GraalVM native images.

The following configuration can be used as a starting point for users wishing to build a native image for a DSE Graph application. DataStax does not guarantee however that the below configuration will work in all cases. If the native image build fails, a good option is to use GraalVM’s Tracing Agent to understand why.

  1. Create the following reflection.json file, or add these entries to an existing file:
[
  { "name": "org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3d0" },
  { "name": "org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal" },
  { "name": "org.apache.tinkerpop.gremlin.structure.Graph",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true
  },
  { "name": "org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true
  },
  { "name": " org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true
  },
  { "name": "org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true
  }
]
  1. When invoking the native image builder, add the following flags:
-H:ReflectionConfigurationFiles=reflection.json
--initialize-at-build-time=org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3d0
--initialize-at-build-time=org.apache.tinkerpop.shaded.jackson.databind.deser.std.StdDeserializer

Using the shaded jar

The shaded jar is not officially supported in a GraalVM native image.

However, it has been reported that the shaded jar can be included in a GraalVM native image as a drop-in replacement for the regular driver jar for simple applications, without any extra GraalVM configuration.