Java has two solid open-source options for PDF-to-JPG: Apache PDFBox and iText. PDFBox is Apache-licensed and the easier dependency to justify in most orgs. iText 7 is more feature-rich but AGPL (commercial license required for SaaS). When you want neither on your classpath, the ChangeThisFile API takes a multipart POST from Java 11's built-in HttpClient — no third-party HTTP library required.
Method 1: Apache PDFBox
PDFBox is the most common choice — Apache 2.0 license, available on Maven Central, no native dependencies.
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>3.0.2</version>
</dependency>
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class PdfToJpg {
public static List<Path> convert(Path pdfPath, Path outDir, float dpi) throws IOException {
Files.createDirectories(outDir);
List<Path> pages = new ArrayList<>();
try (PDDocument doc = Loader.loadPDF(pdfPath.toFile())) {
PDFRenderer renderer = new PDFRenderer(doc);
for (int i = 0; i < doc.getNumberOfPages(); i++) {
BufferedImage img = renderer.renderImageWithDPI(i, dpi, ImageType.RGB);
Path out = outDir.resolve(String.format("page-%03d.jpg", i + 1));
ImageIO.write(img, "JPEG", out.toFile());
pages.add(out);
}
}
return pages;
}
public static void main(String[] args) throws IOException {
List<Path> pages = convert(
Path.of("document.pdf"),
Path.of("./pages"),
200f
);
System.out.println("Wrote " + pages.size() + " pages");
}
}
DPI guide:
- 72 — screen thumbnails only.
- 150 — good default for web previews.
- 200–300 — print-quality; use for documents you'll OCR.
- 600 — archival; large file sizes.
PDFBox 3.x uses Loader.loadPDF() — the older PDDocument.load() is deprecated in 3.x.
Method 2: iText 7
iText 7 gives you finer rendering control and handles some edge-case PDFs better than PDFBox. The license is AGPL — for commercial products, budget for a commercial license.
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>8.0.4</version>
<type>pom</type>
</dependency>
<!-- iText rendering uses pdfRender module -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>pdfRender</artifactId>
<version>3.0.2</version>
</dependency>
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.pdfrender.PdfDocumentRenderer;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class PdfToJpgIText {
public static List<Path> convert(Path pdfPath, Path outDir, int dpi) throws IOException {
Files.createDirectories(outDir);
List<Path> pages = new ArrayList<>();
try (PdfDocument pdf = new PdfDocument(new PdfReader(pdfPath.toFile()))) {
PdfDocumentRenderer renderer = new PdfDocumentRenderer(pdf);
for (int i = 1; i <= pdf.getNumberOfPages(); i++) {
BufferedImage img = renderer.renderPage(i, dpi);
Path out = outDir.resolve(String.format("page-%03d.jpg", i));
ImageIO.write(img, "JPEG", out.toFile());
pages.add(out);
}
}
return pages;
}
public static void main(String[] args) throws IOException {
List<Path> pages = convert(
Path.of("document.pdf"),
Path.of("./pages"),
200
);
System.out.println("Wrote " + pages.size() + " pages");
}
}
iText 7 handles digitally signed PDFs and tagged PDFs more gracefully than PDFBox in many cases. If PDFBox gives you blank pages on a specific PDF, iText is worth trying.
Method 3: ChangeThisFile API (Java 11 HttpClient, no SDK)
Java 11 ships with java.net.http.HttpClient. The multipart body requires a helper since the JDK doesn't include a multipart publisher — the snippet below implements one inline. Free tier covers 1,000 conversions/month (no card).
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class PdfToJpgApi {
private static final String API_KEY = "ctf_sk_your_key_here";
private static final String API_URL = "https://changethisfile.com/v1/convert";
public static byte[] convert(Path pdfPath) throws IOException, InterruptedException {
String boundary = "----CTFBoundary" + UUID.randomUUID().toString().replace("-", "");
byte[] fileBytes = Files.readAllBytes(pdfPath);
String fileName = pdfPath.getFileName().toString();
List<byte[]> parts = new ArrayList<>();
// target field
parts.add(("--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"target\"\r\n\r\njpg\r\n").getBytes(StandardCharsets.UTF_8));
// file field
parts.add(("--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"" + fileName + "\"\r\n" +
"Content-Type: application/pdf\r\n\r\n").getBytes(StandardCharsets.UTF_8));
parts.add(fileBytes);
parts.add(("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8));
int totalLen = parts.stream().mapToInt(b -> b.length).sum();
byte[] body = new byte[totalLen];
int offset = 0;
for (byte[] part : parts) {
System.arraycopy(part, 0, body, offset, part.length);
offset += part.length;
}
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(API_URL))
.header("Authorization", "Bearer " + API_KEY)
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(HttpRequest.BodyPublishers.ofByteArray(body))
.build();
HttpResponse<byte[]> response = client.send(request,
HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) {
throw new IOException("API error: " + response.statusCode() +
" " + new String(response.body()));
}
return response.body();
}
public static void main(String[] args) throws Exception {
byte[] result = convert(Path.of("document.pdf"));
// Multi-page PDFs return a ZIP; single-page returns JPEG directly
String outName = result[0] == 'P' && result[1] == 'K' ? "pages.zip" : "page-001.jpg";
Files.write(Path.of(outName), result);
System.out.println("Saved " + outName + " (" + result.length + " bytes)");
}
}
Multi-page PDFs return a ZIP containing one JPG per page. Detect it by checking the PK magic bytes (0x50 0x4B) at the start of the response body, then extract with java.util.zip.ZipInputStream.
When to use each
| Approach | Best for | Tradeoff |
|---|---|---|
| Apache PDFBox | Apache 2.0 license, no native deps, standard Maven project | Slower than iText on large documents |
| iText 7 | Edge-case PDFs, signed PDFs, tagged accessibility PDFs | AGPL — commercial license required for SaaS |
| ChangeThisFile API | No JAR deps, Lambda/Cloud Run, serverless environments | Network latency, 25MB file limit on free tier |
Production tips
- Use CompletableFuture for batch jobs. PDFBox rendering is CPU-bound and thread-safe after loading. Wrap each page in a
CompletableFuture.supplyAsync()with a boundedForkJoinPoolto parallelize rendering across pages. - Reuse HttpClient across calls.
HttpClientis designed to be long-lived. Create one instance at class level (or inject as a singleton) and reuse it — it manages connection pooling internally. - Control JPEG quality via ImageWriteParam.
ImageIO.write(img, "JPEG", out)defaults to ~75% quality. For higher quality, get aJPEGImageWriter, setImageWriteParam.compressionQuality = 0.90f, and usewriter.write(null, new IIOImage(img, null, null), param). - Dispose renderers and close documents. PDFBox
PDDocumentholds file handles. Always use try-with-resources. Memory leaks from unclosed documents are the #1 PDFBox production issue. - For API calls, set a timeout. Add
.timeout(Duration.ofSeconds(120))to yourHttpRequest.Builder. Large PDFs with many pages can take 30–60 seconds on the first render.
FAQ
Common questions when converting PDF to JPG in Java.
For most Java projects, Apache PDFBox is the right call — Apache 2.0, no native deps, solid rendering. Switch to iText 7 when PDFBox chokes on a specific PDF. For serverless or dependency-light deployments, the ChangeThisFile API with Java 11's HttpClient needs zero third-party JARs. Free tier covers 1,000 conversions/month.