Backend Development 11 min read

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.

Architecture Development Notes
Architecture Development Notes
Architecture Development Notes
Boost PDF-to-Image Conversion in Kotlin with JNI and Rust: A Step-by-Step Guide

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.

rustKotlinNative IntegrationJNIPDF to PNGPDFium
Architecture Development Notes
Written by

Architecture Development Notes

Focused on architecture design, technology trend analysis, and practical development experience sharing.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.