Embedding a PNG into a PDF in Java is straightforward with either iText 7 or Apache PDFBox — both let you control page size, image scaling, and margins. iText 7 has a higher-level API (auto-fit to page) while PDFBox gives you explicit coordinate control. For multi-image PDFs (one PNG per page), both handle it cleanly. When you want neither library in your build, the ChangeThisFile API does it from a HttpClient POST.
Method 1: iText 7 (high-level, auto-fit to page)
iText 7 provides an auto-layout API that scales the image to fit the page with a single call. License is AGPL — commercial license needed for SaaS.
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>8.0.4</version>
<type>pom</type>
</dependency>
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Image;
import com.itextpdf.layout.properties.UnitValue;
import java.nio.file.Path;
import java.util.List;
public class PngToPdfIText {
/** Convert a single PNG to a PDF page, fitting the image to an A4 page. */
public static void convert(Path pngPath, Path pdfPath) throws Exception {
try (PdfWriter writer = new PdfWriter(pdfPath.toFile());
PdfDocument pdf = new PdfDocument(writer);
Document doc = new Document(pdf, PageSize.A4)) {
doc.setMargins(20, 20, 20, 20);
Image img = new Image(ImageDataFactory.create(pngPath.toUri().toURL()));
img.setWidth(UnitValue.createPercentValue(100)); // Fill page width
img.setAutoScaleHeight(true); // Maintain aspect ratio
doc.add(img);
}
}
/** Convert multiple PNGs — one per PDF page. */
public static void convertMultiple(List<Path> pngPaths, Path pdfPath) throws Exception {
try (PdfWriter writer = new PdfWriter(pdfPath.toFile());
PdfDocument pdf = new PdfDocument(writer);
Document doc = new Document(pdf, PageSize.A4)) {
doc.setMargins(20, 20, 20, 20);
for (int i = 0; i < pngPaths.size(); i++) {
Image img = new Image(ImageDataFactory.create(
pngPaths.get(i).toUri().toURL()));
img.setWidth(UnitValue.createPercentValue(100));
img.setAutoScaleHeight(true);
doc.add(img);
if (i < pngPaths.size() - 1) {
doc.add(new com.itextpdf.layout.element.AreaBreak(
com.itextpdf.layout.properties.AreaBreakType.NEXT_PAGE));
}
}
}
}
public static void main(String[] args) throws Exception {
convert(Path.of("image.png"), Path.of("output.pdf"));
System.out.println("Saved output.pdf");
}
}
Method 2: Apache PDFBox (Apache 2.0, explicit coordinates)
PDFBox gives you explicit control over position and size on the page. Apache 2.0 license — safe for commercial use without a paid license.
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>3.0.2</version>
</dependency>
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
public class PngToPdfBox {
public static void convert(Path pngPath, Path pdfPath) throws IOException {
BufferedImage bi = ImageIO.read(pngPath.toFile());
float imgW = bi.getWidth();
float imgH = bi.getHeight();
try (PDDocument doc = new PDDocument()) {
// Size the page to the image (1 point per pixel at 72 DPI)
PDPage page = new PDPage(new PDRectangle(imgW, imgH));
doc.addPage(page);
PDImageXObject pdImage = PDImageXObject.createFromFile(
pngPath.toString(), doc);
try (PDPageContentStream cs = new PDPageContentStream(doc, page)) {
cs.drawImage(pdImage, 0, 0, imgW, imgH);
}
doc.save(pdfPath.toFile());
}
}
/** Convert multiple PNGs — one per PDF page, page sized to each image. */
public static void convertMultiple(List<Path> pngPaths, Path pdfPath) throws IOException {
try (PDDocument doc = new PDDocument()) {
for (Path png : pngPaths) {
BufferedImage bi = ImageIO.read(png.toFile());
PDPage page = new PDPage(new PDRectangle(bi.getWidth(), bi.getHeight()));
doc.addPage(page);
PDImageXObject img = PDImageXObject.createFromFile(png.toString(), doc);
try (PDPageContentStream cs = new PDPageContentStream(doc, page)) {
cs.drawImage(img, 0, 0, bi.getWidth(), bi.getHeight());
}
}
doc.save(pdfPath.toFile());
}
}
public static void main(String[] args) throws IOException {
convert(Path.of("image.png"), Path.of("output.pdf"));
System.out.println("Saved output.pdf");
}
}
PDFBox creates the page in PDF points (72 points = 1 inch). When the image is 1200×900 px at 150 DPI, the resulting PDF page will be 8"×6" when printed at 150 DPI. To enforce A4 size, scale the image to fit within A4's 595×842 points.
Method 3: ChangeThisFile API (Java 11 HttpClient, no SDK)
POST the PNG as multipart. Source is auto-detected. Free tier covers 1,000 conversions/month.
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.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class PngToPdfApi {
private static final String API_KEY = "ctf_sk_your_key_here";
private static final String API_URL = "https://changethisfile.com/v1/convert";
private static final HttpClient HTTP = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
public static byte[] convert(Path pngPath) throws IOException, InterruptedException {
String boundary = "----CTFBoundary" + UUID.randomUUID().toString().replace("-", "");
byte[] fileBytes = Files.readAllBytes(pngPath);
List<byte[]> parts = new ArrayList<>();
parts.add(("--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"target\"\r\n\r\npdf\r\n").getBytes(StandardCharsets.UTF_8));
parts.add(("--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"" + pngPath.getFileName() + "\"\r\n" +
"Content-Type: image/png\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;
}
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(API_URL))
.header("Authorization", "Bearer " + API_KEY)
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.timeout(Duration.ofSeconds(30))
.POST(HttpRequest.BodyPublishers.ofByteArray(body))
.build();
HttpResponse<byte[]> response = HTTP.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[] pdf = convert(Path.of("image.png"));
Files.write(Path.of("output.pdf"), pdf);
System.out.println("Saved output.pdf (" + pdf.length + " bytes)");
}
}
When to use each
| Approach | Best for | Tradeoff |
|---|---|---|
| iText 7 | Auto-scaling to A4, multi-page, layout control | AGPL — commercial license needed for SaaS |
| Apache PDFBox | Apache 2.0, explicit coordinate control, commercial-safe | Lower-level API — you manage scaling and positioning |
| ChangeThisFile API | Zero deps, Lambda, quick single-image conversion | Network round-trip; 25MB limit on free tier |
Production tips
- Scale to A4 with PDFBox by computing the fit ratio. A4 is 595×842 points. Compute
scale = Math.min(595f / imgW, 842f / imgH)and draw withcs.drawImage(img, 0, 842 - imgH*scale, imgW*scale, imgH*scale)to fit the image within an A4 page. - Use CompletableFuture for parallel batch conversion. Both iText 7 and PDFBox are thread-safe — create independent Document/PDDocument objects per thread and combine the results.
- Preserve PNG transparency in the PDF. Both iText 7 and PDFBox support RGBA PNG with transparency. iText maps it to a masked PDF image. If you need a white background, composite the PNG onto a white BufferedImage before conversion.
- Reuse HttpClient for API batches. One HttpClient instance per JVM is correct — it manages connection pooling automatically.
- For CMYK printing, convert to sRGB first. Most PDFs for screen use sRGB. If you're creating print-ready PDFs, iText supports ICC color profiles for CMYK output.
For A4-fitted PDFs with auto-layout, iText 7 is the most concise option. For Apache 2.0 licensing and coordinate control, PDFBox is the standard. For minimal-dependency deployments (Lambda, Cloud Run), the ChangeThisFile API with Java 11's HttpClient needs no extra JARs. Free tier covers 1,000 conversions/month.