Building a Server-Side PDF Processing Pipeline with RadaeePDF

Server-side PDF SDK pipeline processing documents in batch

This guide shows how to build a PDF processing pipeline with a server-side PDF SDK: the RadaeePDF Server engine driven from shell scripts, Java and C# on Windows and Linux.

Applies to: RadaeePDF Server SDK (Java bindings, C# bindings and CLI tools) on Windows and Linux.

Key takeaways:

  • RadaeePDF Server is a server-side PDF SDK: a native C++ engine for Windows and Linux that runs headless.
  • You can drive it from shell scripts with the CLI tools, from Java through JNI, or from C#.
  • The same engine renders pages to images, extracts text, encrypts with AES-256 and merges documents.
  • One server license covers any number of concurrent processes on that machine.

Backend workflows often need PDF processing without any user interface: turning uploaded documents into page images, extracting text for search, protecting files before delivery, or assembling several documents into one. The RadaeePDF Server SDK is built for that job. It is compiled from native C++ code, runs on Windows and Linux, and exposes the same engine through Java, C# and a set of command-line tools.

This guide walks through a pipeline in three layers: shell scripts that call the CLI tools, Java code built on the classes in the PDFJniDemo project, and C# code built on the wrappers shipped in the C# folder. Every call shown here comes from the public RadaeePDF-Server repository.

What the server-side PDF SDK package contains

The repository is organized by integration type:

  • Binaries/: the native engine, RDDJni.dll for Windows (x64 and x86) and libRDDJni.so for Ubuntu 22.04 (x64 and ARM64) and Ubuntu 24.04 (x64).
  • CLI/: ready-to-run executables rdinfo, rdsec, rdpage and rdcvt, plus their C++ sources.
  • java/: the PDFJniDemo Eclipse project (packages com.radaee.comm, com.radaee.pdf, com.radaee.util) and a JSPDemo web sample.
  • C#/: wrapper sources for Windows and Linux (RDDCom.cs, RDDPDF.cs, RDDDOCX.cs) and two demo projects.
  • res/: resource files, including cmaps.dat and umaps.dat, which are required for correct rendering and text extraction.

Choosing the right layer

The three layers share one engine, so the choice is about how the pipeline is operated, not about features:

  • CLI tools fit scheduled jobs, shell-based workers and quick prototypes. Nothing has to be compiled, and every operation is one process call with a clear exit point.
  • Java bindings fit services that already run on the JVM, including servlet containers: the JSPDemo folder shows page rendering, splitting and DOCX conversion called from JSP pages.
  • C# bindings fit .NET services on Windows or Linux. The wrappers are plain source files that you add to your project, and they call the native library through DllImport.

A common pattern is to start with the CLI to validate the output on real documents, then move the same steps into Java or C# when you need finer control, such as custom file naming, per-page error handling or streaming results back to a web request.

Deploying the native engine

The rule repeated across the READMEs is simple: the native library and cmaps.dat/umaps.dat must be deployed together. On Linux, the Binaries README installs them like this:

sudo cp "Binaries/Ubuntu22.04.1 LTS x64/libRDDJni.so" /usr/local/lib/
sudo cp res/cmaps.dat res/umaps.dat /usr/local/lib/
sudo ldconfig

For Java, start the JVM with -Djava.library.path pointing to the folder that holds the library, for example java -Djava.library.path=/usr/local/lib -jar YourApp.jar.

There is one Linux detail worth knowing before your first run. Both Global.Init in the Java demo and RDGlobal.Init in the C# wrapper load the CMaps, the CMYK profile and the fallback fonts from hard-coded paths under /root/res/. On Windows they call a different loader. Edit those paths so they point to your real resource directory, or the engine starts without fonts and character maps.

Layer 1: batch jobs with the CLI tools

The CLI tools are the fastest way to add PDF processing to cron jobs, queues or CI scripts. Copy the whole content of CLI/Ubuntu-22.04-LTS (or CLI/Windows-x64) to the target folder, keep libRDDJni.so, cmaps.dat and umaps.dat next to the executables, and on Linux run chmod +x rdinfo rdsec rdpage rdcvt.

The tools need an activated license. If they report “license not activated”, run ./rdinfo -r to print the request code, obtain a key for it from RadaeePDF, and save the key in a file named key.txt in the program directory.

Each tool takes the file, then its password ("" when there is none):

  • rdinfo -info <file> <pswd> prints document information; rdinfo -set <file> <pswd> Author=... Title=... writes metadata.
  • rdpage -txt <file> <pswd> <list> <out.txt> extracts text; an empty list means all pages.
  • rdpage -exp <file> <pswd> <png|jpg> <dpi> <directory> exports every page as 0000.png, 0001.png and so on. A dpi of 72 means 100%, 144 means 200%.
  • rdpage -del, -rot and -imp delete, rotate (multiples of 90) and import pages.
  • rdsec -enc <file> <pswd> <out> <owner-pswd> <user-pswd> encrypts; rdsec -dec removes the password; rdsec -wm stamps a PNG (RGBA) watermark on every page.

A nightly job that processes a folder of incoming files can be written with nothing but these options:

#!/bin/bash
# libRDDJni.so, cmaps.dat, umaps.dat and key.txt
IN=/data/incoming
OUT=/data/processed

for f in "$IN"/*.pdf; do
  name=$(basename "$f" .pdf)
  mkdir -p "$OUT/$name/pages"

  ./rdinfo -info "$f" "" > "$OUT/$name/info.txt"
  ./rdpage -txt "$f" "" "" "$OUT/$name/text.txt"
  ./rdpage -exp "$f" "" png 144 "$OUT/$name/pages"
  ./rdsec -enc "$f" "" "$OUT/$name/$name-protected.pdf" "owner-secret" ""
done

To merge documents, ./rdpage -imp test.pdf "" src.pdf "" appends all pages of src.pdf to test.pdf, creating test.pdf if it does not exist. Options such as -del, -rot and -imp take no output path, so run them on a copy if you need to keep the original.

Layer 2: Java integration

When the pipeline lives inside a Java service, use the classes from java/PDFJniDemo/src. Initialization is a single call: Global.Init(String serial) loads the RDDJni library, activates the license and returns true on success. Later calls return immediately, so call it once at startup. Without an active license the demo shows that GetPage returns null after the first pages, so always check for it.

The following class renders every page to PNG. It follows the render_pages method of JSPDemo/images.jsp, including its 4096-pixel cap that keeps very large pages from exhausting memory.

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

import com.radaee.comm.Global;
import com.radaee.comm.Matrix;
import com.radaee.pdf.Document;
import com.radaee.pdf.Page;

public class PdfPipeline {

    public static void main(String[] args) throws IOException {
        if (!Global.Init("YOUR-LICENSE-KEY")) {
            System.err.println("License not activated: output will be limited.");
        }
        int err = renderPages(args[0], "", new File(args[1]), 2.0f);
        System.out.println("Result: " + err);
    }

    // Returns 0 on success or the Document.Open error code:
    // -1 password needed, -2 unknown encryption, -3 damaged file, -10 access denied
    static int renderPages(String pdfPath, String password, File outDir, float scale)
            throws IOException {
        Document doc = new Document();
        int err = doc.Open(pdfPath, password);
        if (err != 0) return err;
        outDir.mkdirs();
        try {
            int count = doc.GetPageCount();
            for (int pno = 0; pno < count; pno++) {
                Page page = doc.GetPage(pno);
                if (page == null) break; // license not activated
                float pw = doc.GetPageWidth(pno);
                float ph = doc.GetPageHeight(pno);
                float s = scale;
                if (pw * s > 4096.0f) s = 4096.0f / pw;
                if (ph * s > 4096.0f) s = 4096.0f / ph;
                int iw = (int) (pw * s + 0.5f);
                int ih = (int) (ph * s + 0.5f);

                BufferedImage bimg = new BufferedImage(iw, ih, BufferedImage.TYPE_INT_ARGB);
                Matrix mat = new Matrix(s, -s, 0, ph * s); // flip the PDF y axis
                boolean ok = page.RenderToBuf(bimg, -1, mat); // -1 = white background
                mat.Destroy();
                page.Close();
                if (ok) {
                    ImageIO.write(bimg, "png", new File(outDir, String.format("p%06d.png", pno)));
                }
            }
        } finally {
            doc.Close();
        }
        return 0;
    }
}

Encrypting and merging in Java

Document.EncryptAs writes an encrypted copy. The bundled guide (java/doc/genc.html) explains the arguments: user password, owner password, permission bits (-1 grants all), method 3 for AES-256 (other values use AES-128), and a 32-byte file ID. The method requires a premium license.

import java.security.SecureRandom;
import com.radaee.pdf.Document;

public class PdfSecurity {
    static boolean encrypt(String src, String dst, String ownerPassword) {
        Document doc = new Document();
        if (doc.Open(src, null) != 0) return false;
        byte[] id = new byte[32];
        new SecureRandom().nextBytes(id);
        boolean ok = doc.EncryptAs(dst, "", ownerPassword, -1, 3, id);
        doc.Close();
        return ok;
    }
}

To decrypt, open the file with its password and call doc.SaveAs(path, true), which saves the document with its security removed.

Merging uses Document.ImportStart, which returns a Document.ImportContext. Keep the source document open until you call Destroy() on the context. Importing also requires a premium license.

import com.radaee.pdf.Document;

public class PdfMerge {
    // Appends all pages of srcPath to the end of dstPath
    static boolean append(String dstPath, String srcPath) {
        Document dst = new Document();
        Document src = new Document();
        if (dst.Open(dstPath, null) != 0 || src.Open(srcPath, null) != 0) {
            src.Close();
            dst.Close();
            return false;
        }
        Document.ImportContext ctx = dst.ImportStart(src);
        boolean ok = (ctx != null);
        if (ok) {
            int base = dst.GetPageCount();
            int count = src.GetPageCount();
            for (int i = 0; i < count && ok; i++) {
                ok = ctx.ImportPage(i, base + i);
            }
            ctx.Destroy();
        }
        if (ok) ok = dst.Save();
        src.Close();
        dst.Close();
        return ok;
    }
}

To start from an empty file instead, call Document.Create(path), which returns 0 on success. This is how split.jsp writes each page to its own PDF.

Layer 3: C# integration

The C# wrappers live in the RDDLib.comm and RDDLib.pdf namespaces. The Windows demo calls RDGlobal.Init() and then RDGlobal.Active(key), which returns 3 on success. Rendering uses an RDDIB pixel buffer, and RDDIB can save itself straight to PNG or JPEG, so no imaging library is needed.

using System;
using RDDLib.comm;
using RDDLib.pdf;

public class Program
{
    static int Main(string[] args)
    {
        RDGlobal.Init();
        if (RDGlobal.Active("YOUR-LICENSE-KEY") != 3)
        {
            Console.WriteLine("License activation failed");
            return 1;
        }

        PDFDoc doc = new PDFDoc();
        int err = doc.Open(args[0], "");
        if (err != 0)
        {
            Console.WriteLine("Open failed: " + err);
            return 2;
        }

        const float scale = 2.0f; // 144 dpi
        int count = doc.GetPageCount();
        for (int pno = 0; pno < count; pno++)
        {
            PDFPage page = doc.GetPage(pno);
            if (page == null) break;
            float pw = doc.GetPageWidth(pno);
            float ph = doc.GetPageHeight(pno);
            RDDIB dib = new RDDIB((int)(pw * scale), (int)(ph * scale));
            RDMatrix mat = new RDMatrix(scale, -scale, 0, ph * scale);
            page.RenderPrepare(dib);
            if (page.Render(dib, mat, 2))
                dib.SavePNG(string.Format("p{0:D6}.png", pno));
            page.Close();
        }
        doc.Close();
        return 0;
    }
}

The same PDFDoc class has EncryptAs, SaveAs, ImportStart (returning a PDFImporter with ImportPage and ImportEnd), and GetMeta/SetMeta. The Windows demo also converts Word files: DOCXDoc.ExportPDF(PDFDoc) writes a DOCX document into a PDF created with PDFDoc.Create.

Common pitfalls

  • Missing resource files: cmaps.dat and umaps.dat must sit next to the native library. On Linux, also check the /root/res/ paths in Global.Init or RDGlobal.Init.
  • Library not found on Linux: put libRDDJni.so in a directory listed in LD_LIBRARY_PATH or /etc/ld.so.conf, run ldconfig, and inspect dependencies with ldd libRDDJni.so.
  • Architecture or glibc mismatch: compare file libRDDJni.so with uname -m (x86_64 or aarch64). The Ubuntu 22.04 build needs glibc 2.35+, the 24.04 build glibc 2.39+.
  • Windows runtime: the Visual C++ Redistributable must be installed, and the DLL must match the process bitness (x64 or x86).
  • License-bound features: the source marks EncryptAs, ImportStart and NewPage as premium and SaveAs as professional or premium. The license is tied to a request code derived from hardware and network settings, so changing the network configuration can invalidate it.

Licensing for server deployment

Section 12 of the RadaeePDF licensing fact sheet covers server installation. Installing applications, command-line executables or services on a workstation or server requires a proper license, meaning a valid commercial license with a legitimate activation key. The server license does not restrict the number of concurrent processes or applications that invoke the SDK runtime, so one license covers a worker pool or several services on the same machine. Each workstation or server is identified by a unique ID generated by the operating system on its first run, which is why a new or rebuilt server needs its own request code and key.

Frequently asked questions

Can RadaeePDF run on a server without a user interface?

Yes. The RadaeePDF Server SDK is a native C++ engine for Windows and Linux that runs headless. It is meant for conversions, editing and content manipulation in backend services, batch jobs and command-line tools.

Which languages can I use with the RadaeePDF Server SDK?

Java through the JNI bindings in the PDFJniDemo project, C# through the wrappers in the C# folder, and any shell or scheduler that can call the CLI tools such as rdinfo, rdpage and rdsec.

Does the server license limit concurrent processes?

No. Section 12 of the RadaeePDF licensing fact sheet states that the server license does not restrict concurrent processes or applications invoking the SDK runtime. Each workstation or server is identified by a unique ID generated on its first run.

Related on radaeepdf.com

Where to go next