Skip to content

Graal Native Image Support #180

Closed
Closed

Description

@aherlihy

Hi, does the DuckDB JDBC driver support use from a graal native image?

I tried with v1.1.1 using graalvm-jdk-17.0.12+8.1 using SBT NativeImagePlugin, but it fails with:

Exception in thread "main" java.lang.UnsatisfiedLinkError: Can't load library: <...>/build/debug/libduckdb_java.so_osx_universal
	at org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk.NativeLibrarySupport.loadLibraryAbsolute(NativeLibrarySupport.java:100)
	at java.base@17.0.12/java.lang.ClassLoader.loadLibrary(ClassLoader.java:57)
	at java.base@17.0.12/java.lang.Runtime.load0(Runtime.java:755)
	at java.base@17.0.12/java.lang.System.load(System.java:1957)
	at org.duckdb.DuckDBNative.<clinit>(DuckDBNative.java:52)
	at org.duckdb.DuckDBConnection.newConnection(DuckDBConnection.java:51)
	at org.duckdb.DuckDBDriver.connect(DuckDBDriver.java:41)
	at java.sql@17.0.12/java.sql.DriverManager.getConnection(DriverManager.java:681)
	at java.sql@17.0.12/java.sql.DriverManager.getConnection(DriverManager.java:252)
        ...

on Ubuntu 22.04 LTS (and equivalently build/debug/libduckdb_java.so_osx_universal on MacOS 15.3.1). It works fine from a regular JAR. The build.sbt native image settings are:

 .settings(
    nativeImageInstalled := true,
    nativeImageGraalHome := {
      val executable = "native-image"
      val paths = System.getenv("PATH").split(Pattern.quote(pathSeparator))
      paths.map(Paths.get(_)).find(p => Files.isExecutable(p.resolve(executable))) match {
        case Some(binDir) =>
          binDir.getParent
        case None =>
          throw new MessageOnlyException(s"Could not find binary `$executable` in PATH.")
      }
    },
    nativeImageCommand := nativeImageCommand.value ++ Seq(
      "--no-fallback",
      "--report-unsupported-elements-at-runtime"
    ),

    libraryDependencies ++= Seq(
       ...,
      "org.duckdb" % "duckdb_jdbc" % "1.1.1",
    )

I tried extracting the library files from the DuckDB jar and linking them manually but it just segfaulted, so I would be very appreciative of any help. Thank you!

Activity

  1. staticlibs commented on Mar 30, 2025

    @staticlibs
    Member

    Hi! AOT is unlikely to work in the current version.

    Short term, the idea is to make native library loading more flexible - support loading it from FS without unpacking from JAR, on a default or specified path. When this is working, the Java part, that is pretty thin and does not use reflection, should be easy to compile with Graal. And library loading should be flexible enough to work with Graal.

    Long term, the idea (only an idea at this point) to have DuckDB engine (without Java-specific bits) to be packaged as a shared lib that exposes stable C-API. And to have JDBC impl as Java-only (again, without reflection) that uses Foreign Function & Memory API to work with the engine shared lib over C-API. This way the Java part (that will have more logic than now) can be AOT-compiled as an ordinary Java application, assuming Graal will have the full support for FFM.

    PS: I will comment here when library loading enhancements are done.

  2. xiaobo commented on Jun 9, 2025

    @xiaobo

    any progress? thx

  3. davclark commented on Sep 12, 2025

    @davclark

    To make the context a little more explicit on the above mention, this would unlock embedding in babashka, which is a fast-starting clojure(ish) interpreter that provides a lightweight dag syntax, pipelining, and a dynamic lispy DSL for SQL.

  4. borkdude commented on Sep 12, 2025

    @borkdude

    @davclark Thanks for mentioning babashka. I think I might explore the golang route for building a pod for duckdb since in golang you can statically embed the library in an easier way. Similar work has been done here for sqlite3: https://github.com/babashka/pod-babashka-go-sqlite3

  5. FreifeldRoyi commented on Dec 4, 2025

    @FreifeldRoyi

    Wow, that would be awesome if you could support Graal 🙏🏻
    Is it in the upcoming roadmap?

  6. staticlibs commented on Dec 4, 2025

    @staticlibs
    Member

    @FreifeldRoyi

    The library loading improvements went in in #450. Current Graal-related activity is in #452. Graal support is currently not scheduled for 1.5, though if someone knows how to make AOT-compiled driver to run at least hello-world examples - I can include such changes promptly.

  7. PavelNikolaichev commented on May 10, 2026

    @PavelNikolaichev

    @FreifeldRoyi

    The library loading improvements went in in #450. Current Graal-related activity is in #452. Graal support is currently not scheduled for 1.5, though if someone knows how to make AOT-compiled driver to run at least hello-world examples - I can include such changes promptly.

    A few days ago I've tried creating JNI config for quarkus through trial and error. Maybe I am missing something, but I've set build args to init duckdb at runtime and added a dll from jar file so that native image was able to link it. It did work - I was able to init duckdb, fetch data from the parquet and query it. If you want, I can make an example repo to show what I've done. But maybe I just misunderstand something.

  8. staticlibs commented on Jun 22, 2026

    @staticlibs
    Member

    @PavelNikolaichev

    Hi! Sorry for the delay, if you can create an AOT-compilation hello-world script (that I can add to CI) - it would be great.

  9. PavelNikolaichev commented on Jun 23, 2026

    @PavelNikolaichev

    @PavelNikolaichev

    Hi! Sorry for the delay, if you can create an AOT-compilation hello-world script (that I can add to CI) - it would be great.

    I could try, although initially I've meant that I can provide a sample repo that shows how quarkus native images with graalvm can handle this driver. If I understood correctly, you mean providing a small script that you could compile using graalvm only that runs the hello world with this driver and duckdb, right?

  10. staticlibs commented on Jun 23, 2026

    @staticlibs
    Member

    @PavelNikolaichev

    Yes, any script (a minimal Java example + a set of bash commands I suppose), that can produce a native image of the JDBC driver that runs helloworld, will suffice! If you can point me to a repo, from which I can build a native image - this will work too.

  11. geertjanw commented on Aug 29, 2026

    @geertjanw
    Collaborator

    Confirming that the driver runs in a native image with current versions and no source changes. I tested this with duckdb_jdbc 1.5.5.0 and GraalVM CE 25.0.2 on macOS (Apple Silicon) and Linux x64.

    Two things were needed:

    1. Runtime class initialization, the default since GraalVM for JDK 22. DuckDBNative then extracts the library on the user's machine instead of baking a build-machine path into the image. That is where the build/debug/... path in the original report came from.
    2. Reachability metadata: JNI entries recorded by the tracing agent, plus a resource entry for the platform shared library, which the agent does not record.

    Minimal example, Hello.java:

    import java.sql.*;
    
    public class Hello {
        public static void main(String[] a) throws Exception {
            Class.forName("org.duckdb.DuckDBDriver");
            try (Connection c = DriverManager.getConnection("jdbc:duckdb:");
                 Statement s = c.createStatement();
                 ResultSet r = s.executeQuery("SELECT 42 AS answer, version() AS v")) {
                while (r.next()) System.out.println(r.getInt(1) + " " + r.getString(2));
            }
        }
    }

    Build script, using SDKMAN to select GraalVM, with the driver JAR in the same directory:

    sdk use java 25.0.2-graalce
    JAR=duckdb_jdbc-1.5.5.0.jar
    javac -cp $JAR Hello.java
    
    mkdir -p META-INF/native-image/hello
    java --enable-native-access=ALL-UNNAMED \
      -agentlib:native-image-agent=config-output-dir=META-INF/native-image/hello \
      -cp $JAR:. Hello
    
    cat > META-INF/native-image/hello/resource-config.json <<'EOF'
    { "resources": { "includes": [ { "pattern": "libduckdb_java\\.so_osx_universal" } ] } }
    EOF
    
    native-image --no-fallback --enable-native-access=ALL-UNNAMED \
      -cp $JAR:. -H:ConfigurationFileDirectories=META-INF/native-image/hello -o hello Hello
    ./hello

    Output: 42 v1.5.5

    On Linux x64 the resource pattern is libduckdb_java\\.so_linux_amd64, on Linux ARM libduckdb_java\\.so_linux_arm64, on Windows libduckdb_java\\.so_windows_amd64.

    The agent output is small, around 18 JNI entries under org.duckdb.*, and does not change between platforms. Shipping it inside the JAR under META-INF/native-image/org.duckdb/duckdb_jdbc/ would mean users only need the one line resource entry. Beyond that, either per platform classifier artifacts or a documented way to load a library placed next to the executable would remove the last manual step, since embedding all four libraries costs about 260 MB per image.

    Happy to turn this into a CI script or a PR for the bundled metadata if that is useful.

  12. staticlibs commented on Aug 29, 2026

    @staticlibs
    Member

    @geertjanw

    Thanks for looking into this, this is great to know! A PR will be welcome, perhaps it makes sense to split it into 2 PRs:

    • metadata file for META-INF dir
    • new job in Java.yml, structured the same was as the TCK job - rebuild the driver with composite-actions/linux-build-docker, then compile it into a native image and run a hello world

    Beyond that, either per platform classifier artifacts or a documented way to load a library placed next to the executable would remove the last manual step

    I suggest the latter option (shared lib next to executable), but either of them should be fine.

  13. geertjanw commented on Aug 29, 2026

    @geertjanw
    Collaborator
  14. staticlibs commented on Aug 30, 2026

    @staticlibs
    Member

    Closing as implemented in #850.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions