How to Convert Excel to PDF in Java
Converting Excel to PDF in Java helps applications share reports, invoices, and other spreadsheet documents in a format intended for viewing and printing. In this guide, we’ll use Axyra Sheets for Java to render an Excel workbook as PDF on the server, without installing Excel or running an Office process. We’ll start with a basic conversion, then configure sheet selection, page layout, fonts, watermarks, and PDF output options. For a short walkthrough of Axyra before diving in, see Quick Start.
The one-liner
import io.keikai.axyra.sheets.Workbook;
import java.nio.file.Path;
try (Workbook wb = Workbook.open(Path.of("report.xlsx"))) {
wb.renderPdf(Path.of("report.pdf"));
}
That is the whole conversion. Everything below is about controlling the result.
To stream it to an HTTP response instead of writing a file:
try (Workbook wb = Workbook.open(Path.of("report.xlsx"))) {
byte[] pdf = wb.renderPdf(PdfOptions.builder().build());
response.setContentType("application/pdf");
response.getOutputStream().write(pdf);
}
Choosing what goes in
By default every visible worksheet is rendered. PdfOptions narrows it and adds
document metadata.
import io.keikai.axyra.sheets.io.PdfOptions;
PdfOptions options = PdfOptions.builder()
.title("Q3 Report")
.author("Finance")
.sheetNames("Summary", "Detail") // by name (case-sensitive)
.pageRange("1-4") // only these pages of the result
.bookmarks(true) // one outline entry per sheet
.build();
wb.renderPdf(Path.of("report.pdf"), options);
Sheets can also be selected by 0-based index with .sheets(0, 2, 3). Listing a
hidden sheet explicitly includes it, which is how you render something the
workbook does not show interactively.
sheetNames is case-sensitive and an unknown name fails the render rather
than being silently skipped. Feed it names you read back from
wb.sheet(i).name(), not names typed by hand.
Page layout
Pagination comes from the workbook’s own page setup, so the highest-leverage
control is on the Sheet, before you render:
import io.keikai.axyra.sheets.Sheet;
Sheet sheet = wb.sheet("Summary");
sheet.setPrintArea("A1:H60"); // or null to clear
sheet.setLandscape(true);
sheet.setPaperSize(9); // ECMA-376 ST_PaperSize: 1=Letter, 9=A4
sheet.setPageMargins(0.5, 0.5, 0.75, 0.75, 0.3, 0.3); // L R T B header footer, inches
sheet.setPrintTitleRows("$1:$1"); // repeat the header row on every page
sheet.setPrintGridlines(true);
sheet.setPageScale(80); // percent; disables fit-to-page
To make a wide sheet fit without hand-tuning the scale, override fit-to-page for every selected sheet at render time:
PdfOptions fitted = PdfOptions.builder()
.fitTo(PdfOptions.Fit.WIDTH) // WIDTH | HEIGHT | PAGE
.build();
Fit.WIDTH puts all columns on one page width and lets the height run on —
usually what you want for a wide table. Fit.PAGE squeezes the whole sheet onto
a single page, which on a large sheet produces something unreadable.
Margins are in inches and page scale is a percentage, both matching Excel’s own semantics. See Rendering for the full layout reference.
How many pages will it be?
Useful for progress reporting, or for splitting a big render into chunks:
int pages = wb.sheetPageCount(wb.sheet("Summary").index());
Page counts and the image methods take a sheet index. Resolve it from the name:
a file saved in Evaluation Mode starts with an extra Axyra Evaluation Copy
sheet, so index 0 is not necessarily your first sheet.
Fonts
This is the one that bites in production. The renderer needs the fonts the workbook asks for. Metric-compatible substitutes are bundled only for Calibri (Carlito), Cambria (Caladea), and Arial (Liberation Sans); any other missing font falls back to a font with different metrics, which changes line breaks and therefore pagination. CJK text needs CJK fonts, such as Noto, installed.
The renderer scans the usual macOS and Linux font directories, but not
C:\Windows\Fonts. On Windows, pass that folder, or your application’s font
folder, in fontDirs.
import java.util.List;
PdfOptions withFonts = PdfOptions.builder()
.fontDirs(List.of("/usr/share/fonts", "/opt/corp-fonts"))
.build();
Directories that do not exist are skipped rather than failing the render — which is convenient and also why a typo in a path shows up as subtly different output rather than as an error.
A slim container image usually ships no fonts at all. If output must be
pixel-stable across machines, install the fonts in the image (or mount them) and
point fontDirs at them. “It looks right on my laptop” is not evidence.
Watermarks
PdfOptions marked = PdfOptions.builder()
.watermark("CONFIDENTIAL")
.build();
Encryption and permissions
PdfOptions locked = PdfOptions.builder()
.userPassword("open-me") // required to open the document
.ownerPassword("owner") // required to change permissions
.allowPrint(true)
.allowCopy(false)
.allowModify(false)
.build();
Permissions are requests the PDF format makes of the reader application, not enforcement — anything that can display the document can extract its content. Treat them as intent, and use the user password when you need an actual gate.
Archival output
PdfOptions archival = PdfOptions.builder().pdfA(true).build();
PDF/A embeds fonts fully and drops encryption and transparency, so it cannot be combined with the password options above.
The complete program
Converts every visible worksheet to a landscape, A4, fit-to-width PDF with a repeated header row and per-sheet bookmarks.
import io.keikai.axyra.sheets.Sheet;
import io.keikai.axyra.sheets.SheetType;
import io.keikai.axyra.sheets.Visibility;
import io.keikai.axyra.sheets.Workbook;
import io.keikai.axyra.sheets.io.PdfOptions;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class ExcelToPdf {
public static void main(String[] args) {
Path source = Path.of("report.xlsx");
Path target = Path.of("report.pdf");
try (Workbook wb = Workbook.open(source)) {
List<String> worksheets = new ArrayList<>();
for (int i = 0; i < wb.sheetCount(); i++) {
Sheet sheet = wb.sheet(i);
// Naming a hidden sheet would render it, so skip those too.
if (sheet.sheetType() != SheetType.WORKSHEET
|| sheet.visibility() != Visibility.VISIBLE) {
continue;
}
worksheets.add(sheet.name());
sheet.setLandscape(true);
sheet.setPaperSize(9); // A4
sheet.setPageMargins(0.5, 0.5, 0.75, 0.75, 0.3, 0.3);
sheet.setPrintTitleRows("$1:$1"); // header on every page
}
PdfOptions options = PdfOptions.builder()
.title("Quarterly Report")
.author("Finance")
.sheetNames(worksheets.toArray(new String[0]))
.fitTo(PdfOptions.Fit.WIDTH)
.bookmarks(true)
.fontDirs(List.of("/usr/share/fonts"))
.build();
wb.renderPdf(target, options);
String first = worksheets.get(0);
int pages = wb.sheetPageCount(wb.sheet(first).index());
System.out.println("wrote " + target + "; " + first + " is " + pages + " page(s)");
}
}
}
Other output formats
The same layout engine drives images. Per sheet, to six raster or vector formats:
import io.keikai.axyra.sheets.io.ImageOptions;
import io.keikai.axyra.sheets.io.Renderer;
ImageOptions image = ImageOptions.builder()
.scale(2.0) // 2x nominal size
.gridLines(true)
.page(1) // one page of a multi-page sheet
.build();
int summary = wb.sheet("Summary").index();
byte[] png = wb.renderSheetPng(summary, image);
byte[] svg = wb.renderSheetSvg(summary, image);
byte[] jpg = Renderer.toJpeg(wb, summary, image);
byte[] tiff = Renderer.toTiff(wb, summary, image);
Use targetWidthPx(...) instead of scale(...) when you need an exact pixel
width — the engine derives the scale. For a browser preview rather than
paginated output, HTML export is usually the better fit; see
Rendering.
XPS output is not available. Use PDF for paginated output.
Common mistakes
| Symptom | Cause |
|---|---|
| Line breaks and pagination differ between machines | Different fonts available. Ship the fonts and set fontDirs (on Windows, include C:\Windows\Fonts). |
| Columns spill onto a second page | The sheet’s own scale/fit setting. Use fitTo(Fit.WIDTH) or setPageScale. |
| The header row appears only on page 1 | setPrintTitleRows("$1:$1") was not set. |
| Render fails with an unknown sheet name | sheetNames is case-sensitive and rejects unknown names. Read names from the workbook. |
| PDF/A output rejects the password options | PDF/A forbids encryption. Pick one. |
| Blank or clipped pages | A print area narrower than the data, or a stale one saved in the file. Check sheet.printArea(). |
| Charts render but a map chart is an empty frame | Map charts are read and written but currently render as a placeholder. |
Next steps
- Rendering — the full options reference, page-layout rules, font resolution
- How to Create Excel Reports Automatically in Java — generate the workbook you are converting
- Charts — what renders and how
- Supported Formats — every output format
Beyond Excel-to-PDF Conversion
PDF conversion can be the final step of an automated reporting workflow. Before rendering, Axyra Sheets can populate a workbook from application data, recalculate formulas, and add charts or formatting. You can then save an editable XLSX alongside the PDF, or render individual sheets as images for previews. This allows one workbook to supply both the working spreadsheet and the documents your application distributes.