Boost PDF-to-Image Conversion in Kotlin with JNI and Rust: A Step-by-Step Guide
This tutorial explains how a Kotlin backend can efficiently convert PDF pages to high-resolution PNG images by integrating the native PDFium library through a Rust-based JNI adapter, covering performance benefits, workflow steps, and complete code examples for building and invoking the native module.
As a backend developer I have been using Kotlin for years, appreciating its concise and elegant syntax for clean code.
However, the JVM ecosystem sometimes lacks specific libraries or low-level memory control, which becomes an issue when converting each page of a PDF to high‑resolution PNG images.
Apache PDFBox works for simple cases but often yields inaccurate results, runs out of memory on large files, and takes about 15 seconds to convert 30 high‑resolution pages.
PDFium, the open‑source library behind Chrome’s PDF viewer, provides much more accurate results and can process the same 30 pages in roughly 2 seconds, but it is a native C++ library.
Using Java Native Interface (JNI) together with a small Rust adapter, we can integrate PDFium into a Kotlin (or Java) application.
This article shows how to extend your Kotlin/Java app with a native library like PDFium.
Why not just run a subprocess?
Running a subprocess is simple and safe because it isolates crashes and memory leaks, but JNI allows the native library to run inside the JVM process, offering higher performance, better memory utilization, simpler data exchange, callback support, and the fun of combining Kotlin with Rust.
Workflow
The integration involves the following steps:
Create a Kotlin “library” class that declares native methods.
Obtain PDFium binaries for the target platform, either by downloading pre‑compiled .so/.dylib/.dll files or building from source.
Write a Rust adapter that wraps PDFium calls using the pdfium-render crate and the jni crate.
Compile and link the Rust crate as a dynamic library ( .so , .dylib or .dll ) while ensuring the PDFium binary is linked at runtime.
Load the native library from Kotlin with System.loadLibrary .
Call the native function from Kotlin code to extract PDF pages to PNG files.
1. Create Kotlin class
Define a file PdfiumLibrary.kt with an external fun extractPages(...) declaration.
<code>package fluffy.tigerrr
class PdfiumLibrary {
/**
* Extract each page of a PDF to PNG images.
* Returns the number of pages extracted, or -1 on error.
*/
external fun extractPages(inputFile: String, outputDir: String, targetWidth: Int): Int
}
</code>2. Obtain PDFium binary
Download the appropriate binary for your OS or build it from source following the official PDFium documentation.
3. Write Rust adapter
Add a Cargo.toml with dependencies:
<code>[package]
name = "my-pdfium-bindings"
version = "0.1.0"
edition = "2021"
[dependencies]
pdfium-render = { version = "0.8.27", features = ["image", "thread_safe"] }
image = "^0"
jni = "0.21.1"
[lib]
crate-type = ["cdylib"]
</code>Configure build.rs to link the PDFium library:
<code>fn main() {
println!("cargo:rustc-link-search=native=PATH_TO_PDFIUM_DYLIB_FILE");
println!("cargo:rustc-link-lib=dylib=pdfium");
}
</code>4. Compile Rust library
Run cargo build --release to produce a dynamic library ( .so , .dylib or .dll ) and place it alongside the PDFium binary.
5. Load library in Kotlin
<code>package fluffy.tigerrr
class PdfiumLibrary {
external fun extractPages(inputFile: String, outputDir: String, targetWidth: Int): Int
companion object {
init {
System.loadLibrary("my_pdfium_bindings")
}
}
}
</code>6. Call native function
<code>package fluffy.tigerrr
fun main() {
val lib = PdfiumLibrary()
for (i in 1..3) {
val t1 = System.currentTimeMillis()
val result = lib.extractPages("./test.pdf", "./result", 3000)
val t2 = System.currentTimeMillis()
println("Run #$i: extracted $result pages in ${(t2 - t1) / 1000.0} seconds")
}
}
</code>Running the program prints the number of pages extracted and the time taken for each run.
Conclusion
This guide demonstrates how to integrate PDFium into a Kotlin application via a Rust‑based JNI adapter, a technique applicable to any JVM language and other C‑compatible languages.
Even if this approach is not commonly used in production, understanding the options and mastering multiple tools is always valuable.
Architecture Development Notes
Focused on architecture design, technology trend analysis, and practical development experience sharing.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.