From b9e716e19e5c42cc1247e4442f702f2d870351b7 Mon Sep 17 00:00:00 2001 From: Hubert P Date: Sun, 12 Oct 2025 22:58:17 +0200 Subject: [PATCH] Load DuckDB native library by name first When loading a native library, attempt to first load the library, `duckdb_java`, by name. Only when that fails, loading will revert to the old behaviour of searching via classpath/local file path. This allows for using DuckDB Java client in a setup where native library has been properly extracted (wrt OS/Arch), renamed to follow the specification and placed on JNI search path. For `System.loadLibrary` to work, the native library has to have the correct prefix (`lib` for Mac/Linux, no `lib` for Windows) and suffix (`.so`, `.dll` or `.dylib` for Linux/Windows/Mac, respectively). No architecture suffix is allowed. --- src/main/java/org/duckdb/DuckDBNative.java | 81 +++++++++++++--------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/duckdb/DuckDBNative.java b/src/main/java/org/duckdb/DuckDBNative.java index 25bc92b5f..599ba33dc 100644 --- a/src/main/java/org/duckdb/DuckDBNative.java +++ b/src/main/java/org/duckdb/DuckDBNative.java @@ -15,38 +15,48 @@ final class DuckDBNative { static { - try { - String os_name = ""; - String os_arch; - String os_name_detect = System.getProperty("os.name").toLowerCase().trim(); - String os_arch_detect = System.getProperty("os.arch").toLowerCase().trim(); - switch (os_arch_detect) { - case "x86_64": - case "amd64": - os_arch = "amd64"; - break; - case "aarch64": - case "arm64": - os_arch = "arm64"; - break; - case "i386": - os_arch = "i386"; - break; - default: - throw new IllegalStateException("Unsupported system architecture"); - } - if (os_name_detect.startsWith("windows")) { - os_name = "windows"; - } else if (os_name_detect.startsWith("mac")) { - os_name = "osx"; - os_arch = "universal"; - } else if (os_name_detect.startsWith("linux")) { - os_name = "linux"; - } - String lib_res_name = "/libduckdb_java.so" - + "_" + os_name + "_" + os_arch; + String libname = "duckdb_java"; + boolean loaded = loadLibraryByName(libname); + if (!loaded) { + // Fallback, load from a classpath or from a local file path + loadLibraryFromFile(libname); + } + } - Path lib_file = Files.createTempFile("libduckdb_java", ".so"); + private static void loadLibraryFromFile(String libname) { + String full_libname = "lib" + libname; + String lib_suffix = ".so"; + String os_name = ""; + String os_arch; + String os_name_detect = System.getProperty("os.name").toLowerCase().trim(); + String os_arch_detect = System.getProperty("os.arch").toLowerCase().trim(); + switch (os_arch_detect) { + case "x86_64": + case "amd64": + os_arch = "amd64"; + break; + case "aarch64": + case "arm64": + os_arch = "arm64"; + break; + case "i386": + os_arch = "i386"; + break; + default: + throw new IllegalStateException("Unsupported system architecture"); + } + if (os_name_detect.startsWith("windows")) { + os_name = "windows"; + } else if (os_name_detect.startsWith("mac")) { + os_name = "osx"; + os_arch = "universal"; + } else if (os_name_detect.startsWith("linux")) { + os_name = "linux"; + } + String lib_res_name = "/" + full_libname + lib_suffix + + "_" + os_name + "_" + os_arch; + try { + Path lib_file = Files.createTempFile(full_libname, lib_suffix); URL lib_res = DuckDBNative.class.getResource(lib_res_name); if (lib_res == null) { System.load(Paths.get("build/debug", lib_res_name).normalize().toAbsolutePath().toString()); @@ -61,6 +71,15 @@ final class DuckDBNative { throw new RuntimeException(e); } } + + private static boolean loadLibraryByName(String libname) { + try { + System.loadLibrary(libname); + return true; + } catch (Throwable e) { + return false; + } + } // We use zero-length ByteBuffer-s as a hacky but cheap way to pass C++ pointers // back and forth