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

ApproachBest forTradeoff
iText 7Auto-scaling to A4, multi-page, layout controlAGPL — commercial license needed for SaaS
Apache PDFBoxApache 2.0, explicit coordinate control, commercial-safeLower-level API — you manage scaling and positioning
ChangeThisFile APIZero deps, Lambda, quick single-image conversionNetwork 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 with cs.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.