Sheet Layout & Printing
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
Baseline date: 2026-10-09. This page was written against Apache POI 5.5.1 (the latest release on that date; 6.0 has not
been released), Java 21 and later, Spring Boot 4.x on Spring Framework 7 and Jakarta EE 11. A sheet that holds correct
values is only half of a report: the reader of books-inventory.xlsx also needs a header row that stays visible, columns
wide enough for the titles, a filter on the catalogue, and a printout that fits the paper with a page number in the
footer. All of it is layout and page setup data that lives in the sheet and in the workbook, not in the cells, and all
of it is reachable from the Sheet interface of the ss.usermodel API that
Spreadsheet Usermodel Basics introduces.
The examples use the Bookshelf scenario described in the section landing page. Cell formatting (fonts, fills, number formats) is in Cell Styles, Fonts & Formats; the validation and conditional-formatting rules that normally accompany a styled catalogue are in Data Validation & Conditional Formatting. This page covers everything that is neither a value nor a cell style.
What lives where
Layout settings are stored in different places of the file. Knowing the place explains which are cheap, which interact with each other and which survive a round trip through another tool.
| Setting | OOXML (.xlsx) location |
OLE2 (.xls) record |
|---|---|---|
Column widths, hidden columns, column outline level |
|
|
Row height, hidden row, row outline level |
attributes of each |
|
Freeze / split panes, zoom, gridlines, selected cell |
|
|
Outline direction (summary rows) |
|
|
Auto-filter |
|
|
Paper size, orientation, scaling, fit to page |
|
|
Margins, centring, print gridlines |
|
|
Headers and footers |
|
|
Print area, repeating rows and columns |
workbook-level defined names |
|
Manual page breaks |
|
|
Active and selected sheet, hidden sheet |
|
|
Two consequences matter in practice. First, the print area and the repeating rows are defined names (see Tables, Named Ranges & Pivot Tables): POI creates them for you, but a workbook that already defines a name with the same built-in identifier for the same sheet is overwritten. Second, the auto-filter also creates a hidden name, which is why an auto-filter range that is later changed must be set again rather than edited.
Column widths
The unit
Sheet.setColumnWidth(int columnIndex, int width) takes the width in 1/256 of the width of a character of the
workbook’s default font (the digit 0 of the Normal style, normally Calibri 11 or Arial 10). A width of 10 * 256 is
ten characters. The same unit is returned by getColumnWidth(int); getColumnWidthInPixels(int) returns a float
for a pixel-oriented view. The practical limits are:
| Setting | Limit | Notes |
|---|---|---|
Width of one column |
255 characters, that is |
A larger value throws |
Default width |
|
Applies to every column without an explicit width. |
Columns per sheet |
16,384 ( |
|
Fixed widths are the cheapest and the most predictable way to lay out a report: they cost nothing at write time, work in any environment and stay identical on every machine. For a catalogue whose columns you know (ISBN, title, author, price, stock), a constant per column is usually the right answer.
autoSizeColumn
Sheet.autoSizeColumn(int column) sets the width to fit the widest rendered value of the column; the overload
autoSizeColumn(int column, boolean useMergedCells) decides whether cells that belong to merged regions take part (the
default is false, because a merged title that spans six columns would otherwise widen the first one). POI measures the
text with java.awt font metrics, a fact with consequences:
-
It needs fonts and AWT classes on the server. Set
-Djava.awt.headless=true(orSystem.setProperty("java.awt.headless", "true")before the first call) so the JVM does not look for a display. On a minimal container image also install a font set andfontconfig/freetype; without them the call can fail with ajava.lang.Erroror anUnsatisfiedLinkErrorfrom the font code, or silently measure with a fallback font. A distroless image is the usual place to find out. -
The font must match. The width is computed with the font of each cell; if the workbook specifies Calibri and the server only has DejaVu, the result differs from what Excel would compute and a few characters may be cut off.
-
It costs time. XSSF and HSSF walk every cell of the column and lay out its text, so the cost is proportional to the number of rows. For a sheet with 100,000 rows it is noticeable (often more than the cost of writing the sheet); call it once per column, after the data is written and never inside the row loop, and prefer fixed widths for large exports.
-
Formulas have no value yet. A formula cell of a workbook that was just created has no cached result, so there is little to measure; size the columns of computed values by hand or evaluate the formulas first (see Formulas & Evaluation).
-
It ignores the header filter button. An auto-filter drop-down arrow covers about two characters of the first row; add a small margin (
sheet.setColumnWidth(i, sheet.getColumnWidth(i) + 2 * 256)) so the first title is not hidden. -
It is a snapshot. The width is stored; later edits do not re-measure.
The SXSSFSheet variant is different and is described in Autosizing in SXSSF.
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
public class BooksInventoryColumns {
public static void main(String[] args) throws IOException {
System.setProperty("java.awt.headless", "true");
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("Books");
Font bold = workbook.createFont();
bold.setBold(true);
CellStyle header = workbook.createCellStyle();
header.setFont(bold);
String[] titles = {"ISBN", "Title", "Author", "Price", "Stock"};
Row headerRow = sheet.createRow(0);
for (int column = 0; column < titles.length; column++) {
Cell cell = headerRow.createCell(column);
cell.setCellValue(titles[column]);
cell.setCellStyle(header);
}
for (int index = 1; index <= 120; index++) {
Row row = sheet.createRow(index);
row.createCell(0).setCellValue("978014044" + (9000 + index));
row.createCell(1).setCellValue("A long book title number " + index);
row.createCell(2).setCellValue("Author " + index);
row.createCell(3).setCellValue(10 + index * 0.5);
row.createCell(4).setCellValue(index % 7);
}
sheet.setDefaultColumnWidth(12); // 12 characters unless overridden
sheet.setColumnWidth(0, 15 * 256); // fixed: ISBN is always 13 digits
sheet.autoSizeColumn(1); // measured: titles vary
sheet.autoSizeColumn(2, true); // measured, merged cells included
sheet.setColumnWidth(3, 10 * 256); // fixed: price
// make room for the filter arrow in the header cell of the measured columns
sheet.setColumnWidth(1, sheet.getColumnWidth(1) + 2 * 256);
sheet.setColumnWidth(2, sheet.getColumnWidth(2) + 2 * 256);
sheet.setAutoFilter(new CellRangeAddress(0, 120, 0, 4));
try (OutputStream out = Files.newOutputStream(Path.of("books-inventory.xlsx"))) {
workbook.write(out);
}
}
}
}
Autosizing in SXSSF
SXSSFSheet writes rows to a temporary file as soon as they leave the sliding window, so it cannot look at the whole
column afterwards. Instead it keeps, per tracked column, the widest value seen so far:
| Method | Behaviour |
|---|---|
|
Start tracking one column. |
|
Start tracking several columns. |
|
Track every column. Convenient, but it measures text for every cell of every row, so it is the slowest option. |
|
Stop tracking (frees the bookkeeping and the measurement cost). |
|
Applies the tracked width. Throws |
The tracking must be enabled before the rows are created: only rows written after the call take part. In a test with
50 rows written, then tracking enabled, then 50 longer rows, the width reflected only the second batch. A sensible order
is therefore: create the sheet, declare the columns to track, write the rows, call autoSizeColumn, write the workbook.
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
public class LoansReportAutosize {
public static void main(String[] args) throws IOException {
System.setProperty("java.awt.headless", "true");
// keep 100 rows in memory; close() also deletes the temporary files
try (SXSSFWorkbook workbook = new SXSSFWorkbook(100)) {
SXSSFSheet sheet = workbook.createSheet("Loans");
sheet.trackColumnForAutoSizing(0); // only the borrower column is measured
sheet.trackColumnForAutoSizing(1);
for (int index = 0; index < 100_000; index++) {
Row row = sheet.createRow(index);
row.createCell(0).setCellValue("Borrower " + index);
row.createCell(1).setCellValue("The Odyssey, copy " + (index % 12));
row.createCell(2).setCellValue(index % 30);
}
sheet.autoSizeColumn(0);
sheet.autoSizeColumn(1);
sheet.setColumnWidth(2, 8 * 256); // fixed: no need to track
try (OutputStream out = Files.newOutputStream(Path.of("loans-report.xlsx"))) {
workbook.write(out);
}
}
}
}
Source: POI How-To, SXSSF and the SXSSFSheet Javadoc
The streaming workbook itself (window size, flushing, close(), temp files) is the topic of
Streaming Writes with SXSSF. For a very large export the
advice stays the same as for XSSF: fixed widths for all columns whose content you know, and tracking only for the one
or two free-text columns.
Row heights
| Method | Notes |
|---|---|
|
The usual way. Excel’s default is 15 points (20 pixels) for a Calibri 11 sheet. |
|
The same value in twips (1/20 of a point), so 15 points is 300. Excel allows at most 409 points. |
|
The height of rows that do not specify one. |
|
Hides the row ( |
|
The effective height (the default if none is set). |
POI never computes a row height from the content. When a cell has CellStyle.setWrapText(true) and a long value, either
leave the height unset and let the spreadsheet application decide (Excel and LibreOffice normally size rows without a
custom height when they open the file; simpler viewers may not), or set a height yourself from the number of lines and
the font height. Setting a height is a custom height: the application will not shrink or grow that row later.
Freeze and split panes
A freeze pane keeps the first rows and columns visible while the rest scrolls. It is a view setting: it does not change the data and is not printed.
| Method | Effect |
|---|---|
|
Freezes the |
|
Also sets the first cell of the scrolling area, useful when the sheet should open scrolled down. |
|
A real split (two to four independently scrolling panes). The positions are in 1/20 of a point, not in cells.
|
|
Returns a |
Calling createFreezePane(0, 0) (both arguments zero) removes the pane (getPaneInformation() then returns null in a test on XSSF). Freeze panes work on SXSSFSheet at any
time because they are part of the sheet header, not of the row data: they are written before the rows.
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.poi.ss.usermodel.PaneType;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
public class LoansPanes {
public static void main(String[] args) throws IOException {
try (XSSFWorkbook workbook = new XSSFWorkbook()) {
// 1. Freeze pane: header row and the first two columns stay visible.
XSSFSheet books = workbook.createSheet("Books");
books.createRow(0).createCell(0).setCellValue("ISBN");
books.createFreezePane(2, 1);
// 2. Split pane: the positions are in 1/20 of a point (3000 = 150 pt, 1500 = 75 pt).
XSSFSheet loans = workbook.createSheet("Loans");
loans.createRow(0).createCell(0).setCellValue("Borrower");
loans.createSplitPane(3000, 1500, 3, 10, PaneType.LOWER_RIGHT);
try (OutputStream out = Files.newOutputStream(Path.of("loans-panes.xlsx"))) {
workbook.write(out);
}
}
}
}
Use the PaneType overload. The older createSplitPane(int, int, int, int, int activePane) that takes a
Sheet.PANE_* constant is deprecated and marked @Removal(version="7.0.0"): its PANE_* values are the HSSF ones,
while XSSF converts them with the 1-based STPane enumeration, so PANE_LOWER_RIGHT (0) wrote an empty activePane
(the Javadoc of that overload points at POI bug 66173). In a test on 5.5.1 the PaneType.LOWER_RIGHT call above wrote
<pane xSplit="3000.0" ySplit="1500.0" state="split" topLeftCell="D11" activePane="bottomRight"/>, so
leftmostColumn and topRow do reach the file as topLeftCell, and no low-level CTPane workaround is needed. Split
panes are still less common than freeze panes, so check the result in a real viewer.
Grouping and outlining
Outline groups let the reader collapse detail rows or columns behind a plus/minus button. They are used for subtotals (for example loans grouped per month under a monthly total) and for optional columns.
| Method | Effect |
|---|---|
|
Adds (removes) one outline level to the rows, zero based and inclusive. Calling it again on an overlapping range nests the groups; Excel supports up to seven levels. |
|
The same for columns. |
|
Collapses or expands the group that contains |
|
The same for columns. |
|
Where the summary row or column sits, which decides which row gets the plus/minus button. The defaults are |
|
The level of an existing row, for example to inspect an uploaded file (the |
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
public class LoansGrouping {
public static void main(String[] args) throws IOException {
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("Loans by month");
for (int index = 0; index < 14; index++) {
Row row = sheet.createRow(index);
row.createCell(0).setCellValue(index % 7 == 6 ? "Total" : "Loan " + index);
}
sheet.setRowSumsBelow(false); // the total row sits above the detail
sheet.groupRow(1, 5); // January detail: rows 2-6 in Excel numbering
sheet.groupRow(8, 12); // February detail
sheet.setRowGroupCollapsed(1, true); // January starts collapsed
sheet.groupColumn(2, 4); // the three optional columns C:E
sheet.getRow(13).setZeroHeight(true); // a hidden helper row
sheet.setColumnHidden(5, true); // a hidden helper column
try (OutputStream out = Files.newOutputStream(Path.of("loans-grouped.xlsx"))) {
workbook.write(out);
}
}
}
}
Source: POI Quick Guide, Grouping
In SXSSFSheet grouping must happen while the rows are still in the sliding window. In a test with a window of 10 rows
and 100 written rows, groupRow on rows already flushed to disk did not throw but wrote no outlineLevel, and
setRowGroupCollapsed on such a row threw IllegalArgumentException ("Row does not exist"). Grouping rows 92 to 96
while they were still in memory wrote outlineLevel="1", hidden="1" on the detail rows and collapsed="1" on the
summary row. The practical rule: call groupRow and setRowGroupCollapsed right after writing the group, before the
window moves past it (or use a window as large as the largest group). Column grouping has no such limit because column
data is not streamed.
Hidden rows, columns and sheets
| Method | Notes |
|---|---|
|
Hides a row. The data stays in the file and in formulas. |
|
Hides a column. A hidden column keeps its stored width, which is restored when it is shown again. |
|
|
|
View options of the sheet. They only affect the screen; gridline printing is |
|
The colour of the sheet tab (XSSF only; the |
Hidden is not secret. A hidden row, column or sheet, even a VERY_HIDDEN one, is plain data in the file and appears in
every extraction tool. Do not put values in them that the reader must not see; use a separate document or real
encryption (see Introduction & Architecture for the
component map and the later pages on encryption and protection).
Zoom
Sheet.setZoom(int percent) sets the zoom of the view, between 10 and 400 (values outside throw
IllegalArgumentException). setZoom(120) is a good choice for a dense catalogue opened on a laptop. The
setZoom(int numerator, int denominator) form exists only on HSSFSheet (it is not deprecated, but the Sheet
interface has only setZoom(int)); use the percentage. The zoom is a view setting and does not
influence the printout, which has its own scale (see Page setup).
Auto-filter
Sheet.setAutoFilter(CellRangeAddress range) shows the drop-down arrows in the first row of the range and returns an
AutoFilter object. Neither AutoFilter nor XSSFAutoFilter has any public method, so pre-set criteria need the
low-level CTAutoFilter bean of the sheet (getCTWorksheet().getAutoFilter()). The range should include the
header row and all data rows. POI also creates the hidden defined name _xlnm._FilterDatabase that Excel expects.
| Aspect | Behaviour |
|---|---|
Filtering |
POI does not filter anything. The arrows are shown and Excel applies the criteria the user picks. If you set criteria in
the file, you must also hide the rows that do not match ( |
Sorting |
Not provided either. Sort the data before writing it. |
One per sheet |
A sheet has at most one auto-filter. For several independent filter ranges use a table (see Tables, Named Ranges & Pivot Tables), which carries its own filter. |
|
Supported. The range is a sheet property and does not depend on the rows being in memory. |
Removal |
The |
The first example of this page already uses setAutoFilter: the range new CellRangeAddress(0, 120, 0, 4) covers the
header and 120 rows of five columns.
Source: POI Quick Guide, Autofilter
Page setup
The settings in this section decide how the sheet looks on paper or in a PDF produced by Excel or LibreOffice. POI only stores them; nothing in POI paginates, so it cannot tell you how many pages the sheet will print (see What POI does not provide).
PrintSetup
Sheet.getPrintSetup() returns a PrintSetup bound to the sheet:
| Method | Meaning |
|---|---|
|
One of the |
|
|
|
Fixed print scale in percent (10 to 400). Ignored when fit to page is on. |
|
Number of pages across and down to fit the sheet into. A value of 0 means "as many as needed" in that direction, so
|
|
Distance of the header and footer from the page edge, in inches. |
|
Number of copies, black-and-white, draft quality, and the page order when the sheet is wider and taller than a page
( |
|
First page number, when it should not start at 1. |
|
Print resolution in dpi, rarely needed. |
Other page-level properties are on the Sheet:
| Method | Meaning |
|---|---|
|
Enables the fit-to-page scaling. Without it, |
|
Page margins in inches. |
|
Center the printed range on the page. |
|
Print the gridlines, or the row numbers and column letters. |
|
Whether Excel adds page breaks on its own (the default) or only the manual ones apply. |
|
Manual page break after the row or column; |
Margins are defined in inches (A4 landscape with 0.5 inch left and right and 0.75 inch top and bottom is a common report layout).
Print area and repeating rows
The print area and the title rows are workbook-level names stored with the sheet:
-
Workbook.setPrintArea(int sheetIndex, int startColumn, int endColumn, int startRow, int endRow)orsetPrintArea(int sheetIndex, String reference)(for example$A$1:$E$121) defines_xlnm.Print_Area.getPrintArea(int)reads it andremovePrintArea(int)deletes it. Note that the string form takes the reference without the sheet name ($A$1:$E$121), whilegetPrintAreareturns it with the sheet name (Books!$A$1:$E$121). -
Sheet.setRepeatingRows(CellRangeAddress)andsetRepeatingColumns(CellRangeAddress)define_xlnm.Print_Titles: the rows (or columns) that repeat on every printed page. UseCellRangeAddress.valueOf("1:1")for the first row andCellRangeAddress.valueOf("A:A")for the first column; passnullto remove them.
The repeating header row is the single most useful print setting for a catalogue that runs to several pages.
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.poi.hssf.usermodel.HeaderFooter;
import org.apache.poi.ss.usermodel.Footer;
import org.apache.poi.ss.usermodel.Header;
import org.apache.poi.ss.usermodel.PageMargin;
import org.apache.poi.ss.usermodel.PrintSetup;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
public class BooksInventoryPrint {
public static void main(String[] args) throws IOException {
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("Books");
for (int index = 0; index <= 120; index++) {
Row row = sheet.createRow(index);
row.createCell(0).setCellValue(index == 0 ? "ISBN" : "978014044" + (9000 + index));
row.createCell(1).setCellValue(index == 0 ? "Title" : "Book " + index);
}
// paper: A4 landscape, one page wide and as many pages tall as needed
PrintSetup setup = sheet.getPrintSetup();
setup.setPaperSize(PrintSetup.A4_PAPERSIZE);
setup.setLandscape(true);
sheet.setFitToPage(true);
setup.setFitWidth((short) 1);
setup.setFitHeight((short) 0);
// margins in inches, centred horizontally
sheet.setMargin(PageMargin.LEFT, 0.5);
sheet.setMargin(PageMargin.RIGHT, 0.5);
sheet.setMargin(PageMargin.TOP, 0.75);
sheet.setMargin(PageMargin.BOTTOM, 0.75);
sheet.setHorizontallyCenter(true);
// what to print and what repeats
workbook.setPrintArea(0, 0, 1, 0, 120); // A1:B121
sheet.setRepeatingRows(CellRangeAddress.valueOf("1:1")); // header row on every page
sheet.setRowBreak(59); // manual break after row 60
// header and footer
Header header = sheet.getHeader();
header.setLeft("Bookshelf");
header.setCenter(HeaderFooter.fontSize((short) 14) + "Books inventory");
header.setRight(HeaderFooter.date());
Footer footer = sheet.getFooter();
footer.setCenter("Page " + HeaderFooter.page() + " of " + HeaderFooter.numPages());
try (OutputStream out = Files.newOutputStream(Path.of("books-inventory-print.xlsx"))) {
workbook.write(out);
}
}
}
}
The class compiled and produced a file with these elements in a test on 5.5.1: <pageSetup paperSize="9"
orientation="landscape" fitToWidth="1" fitToHeight="0"/>, <headerFooter> with &C&14Books inventory and
&CPage &P of &N, and the defined names _xlnm.Print_Area and _xlnm.Print_Titles.
HeaderFooter is an interface in org.apache.poi.ss.usermodel and an abstract class with static helper methods in
org.apache.poi.hssf.usermodel. The helpers (page(), numPages(), date(), time(), file(), sheetName(),
fontSize(short), font(String, String), startBold(), endBold() …) live only in the HSSF class, but they just
return the format codes of the next section, so they work for XSSF too. Import the HSSF class explicitly, as in the
example, or write the codes yourself.
Headers and footers
Sheet.getHeader() and getFooter() return the odd-page (the default) header and footer, each with setLeft,
setCenter and setRight and the matching getters. An XSSFSheet also offers getOddHeader(), getEvenHeader() and
getFirstHeader() (and the matching footers) for a different first page or different odd and even pages; whether
Excel uses them depends on the differentFirst and differentOddEven flags of the <headerFooter> element, which you
set on the CTHeaderFooter bean (sheet.getCTWorksheet().getHeaderFooter()). HSSFSheet only has the single header
and footer.
The text is a string with & codes, the same ones Excel shows in its dialog:
| Code | Inserts |
|---|---|
|
Current page number / total number of pages. |
|
Print date / print time. |
|
File name / sheet name. |
|
File path. |
|
Toggle bold, italic, underline, double underline, strikethrough. |
|
Font size in points, for example |
|
Font name and style for the following text. |
|
Picture placeholder (the image itself is not settable through the |
|
A literal ampersand. A book title such as "Pride & Prejudice" must be written as |
Limits and pitfalls: Excel truncates a header or footer at 255 characters, counting the codes; text entered by users
(titles, author names) should be escaped for &; the six areas share the width of the page, so long text in the left
part overlaps the centre. The header is stored once per sheet and is not a row, so it is not visible in the sheet and not
copied by cell-based tools.
Selected and active sheet
When a workbook opens, Excel shows the active sheet and highlights the selected ones:
| Method | Meaning |
|---|---|
|
The sheet shown when the file opens. |
|
Which tabs are selected. More than one selected tab is a group edit in Excel (a change on one sheet applies to all), so select exactly one unless you mean it. |
|
The first tab shown in the tab bar when there are many sheets. |
|
Which cell is at the top-left of the scrollable area when the file opens. |
|
The selected cell on that sheet. Use |
A known trap in workbooks built from a template (see
Templates & Modifying Workbooks) is two selected sheets:
the template’s tab is still selected after another sheet was activated, so both tabs look selected and the user edits
both at once. Call setActiveSheet and then setSelected(false) on every other sheet.
What POI does not provide
-
No pagination. POI stores page-setup values; it cannot compute how many pages a sheet will print or place the manual breaks for you. To know the page count you have to render the file in a spreadsheet application (LibreOffice in headless mode converts
.xlsxto PDF; POI itself never converts to PDF, see the PDF conversion alternatives in the PDF Generation section). -
No layout preview. There is no API to render a sheet to an image; use LibreOffice or a viewer to check the result.
-
Fonts depend on the server.
autoSizeColumnneeds AWT and installed fonts, and the final widths in the user’s Excel may differ from the server’s measurement. -
No filter or sort engine. The auto-filter is a view of criteria that Excel evaluates.
-
No portable print layout. Different viewers honour different subsets of page setup (many mail and mobile previews ignore fit-to-page, repeating rows and headers). The settings are written for Excel and LibreOffice.
Memory and cost
| Feature | Cost | Notes |
|---|---|---|
Fixed column widths, freeze pane, zoom, page setup, print area, headers and footers |
Negligible |
A few bytes in the sheet header or the workbook. |
|
O(rows) per column, CPU bound |
Text layout per cell. Call once per column after filling the data. |
|
O(rows x columns) while writing |
Measures every written cell; track only the columns you need. |
Row heights, hidden rows |
One attribute per touched row |
Negligible. |
Grouping |
One attribute per grouped row |
Outline data is per row; groups of rows in SXSSF must be defined inside the window. |
|
Negligible |
A range plus a defined name. |
Changes between POI versions
| Version | Change relevant to layout and printing |
|---|---|
3.x to 4.0 |
Enum-based APIs ( |
4.x to 5.x |
The |
5.5.x |
The deprecated |
6.0 (planned, not released) |
The POI site announces removal of the APIs deprecated in 5.x and a raise of the baseline to JDK 17. Code that uses the
|
References
-
Spreadsheet Quick Guide (column widths, freeze panes, grouping, autofilter, headers and footers, print setup): https://poi.apache.org/components/spreadsheet/quick-guide.html
-
Spreadsheet How-To (SXSSF): https://poi.apache.org/components/spreadsheet/how-to.html
-
Sheet Javadoc: https://poi.apache.org/apidocs/dev/org/apache/poi/ss/usermodel/Sheet.html
-
PrintSetup Javadoc: https://poi.apache.org/apidocs/dev/org/apache/poi/ss/usermodel/PrintSetup.html
-
SXSSFSheet Javadoc: https://poi.apache.org/apidocs/dev/org/apache/poi/xssf/streaming/SXSSFSheet.html
-
Excel specifications and limits: https://support.microsoft.com/en-us/office/excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3
-
Official examples: https://github.com/apache/poi/tree/trunk/poi-examples/src/main/java/org/apache/poi/examples/
-
ECMA-376 (SpreadsheetML worksheet, page setup and header/footer syntax): https://ecma-international.org/publications-and-standards/standards/ecma-376/