Word Headers, Footers, Sections & Fields
|
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; the 6.0 line
is in development and has not been released), Java 21 and later, Spring Boot 4.x on Spring Framework 7 and Jakarta EE 11.
It covers everything in a Word document that lives around or between the paragraphs of the body: the running headers and
footers, the page numbers and other fields, the sections that carry the page setup, page breaks, the table of contents,
footnotes, endnotes, review comments and document protection. All of these are part of the .docx format and are reached
through XWPF in the poi-ooxml jar. The examples build the Bookshelf loan-letter.docx and monthly-report.docx
described in the section landing page. The paragraph and run model they build on is
explained in Word Documents with XWPF, tables, lists and pictures in
Word Tables, Lists & Images, replacing placeholders in headers
and footers in
Word Templates & Placeholder Replacement, and the
packaging underneath in
File Formats: OOXML & OLE2.
What lives where in a .docx
Most of the features on this page are not stored in the body of the document. They are separate parts of the OPC package, connected to the main document part by relationships:
| Feature | Package part | XWPF entry point |
|---|---|---|
Header |
|
|
Footer |
|
|
Section (page size, margins, orientation, which header applies) |
An element |
|
Footnotes and endnotes |
|
|
Comments |
|
|
Fields (page number, date, table of contents) |
|
|
Document settings and protection |
|
|
The consequence is practical: a header, a footer, a footnote and a comment are each a small document of their own. They
have their own paragraphs, tables and pictures, and the same editing methods as the body (createParagraph,
createTable), because XWPF models all of them with the interface IBody.
body"] S1["w:sectPr
section 1"] S2["w:sectPr
last section"] H1["header1.xml
default header"] F1["footer1.xml
default footer"] H2["header2.xml
landscape header"] FN["footnotes.xml"] CM["comments.xml"] ST["settings.xml
updateFields, protection"] DOC --> S1 DOC --> S2 S1 -->|"headerReference"| H1 S1 -->|"footerReference"| F1 S2 -->|"headerReference"| H2 S2 -->|"footerReference"| F1 DOC -->|"footnoteReference"| FN DOC -->|"commentReference"| CM DOC -.->|"relationship"| ST
The diagram shows the key rule of Word headers and footers: a header or footer belongs to a section, and a section
points at it with a headerReference or footerReference of a given type. Two sections can share one footer part, as the
two footer references above do.
Headers and footers
Creating a header and a footer
The simplest API is on the document. XWPFDocument.createHeader(HeaderFooterType) and
createFooter(HeaderFooterType) create the part, register it in the section of the body and return an XWPFHeader or
XWPFFooter. Both extend XWPFHeaderFooter, so they offer createParagraph(), createTable(rows, cols),
getParagraphs(), getTables(), getText() and addPictureData(...).
HeaderFooterType lives in org.apache.poi.wp.usermodel and has three values:
| Value | Where Word uses it |
|---|---|
|
Every page that no more specific type covers. In a document without the two options below, this is the only type that is ever shown. |
|
The first page of the section, but only when the section has the "different first page" option ( |
|
Even-numbered pages, but only when the document has the "different odd and even pages" option
( |
The following program writes the Bookshelf loan letter with a right-aligned header and a centred footer that shows "Page X of Y".
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.poi.wp.usermodel.HeaderFooterType;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFFieldRun;
import org.apache.poi.xwpf.usermodel.XWPFFooter;
import org.apache.poi.xwpf.usermodel.XWPFHeader;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
public class LoanLetterWithHeaderFooter {
public static void write(Path target) throws IOException {
try (XWPFDocument document = new XWPFDocument()) {
XWPFHeader header = document.createHeader(HeaderFooterType.DEFAULT);
XWPFParagraph headerParagraph = header.createParagraph();
headerParagraph.setAlignment(ParagraphAlignment.RIGHT);
XWPFRun headerRun = headerParagraph.createRun();
headerRun.setText("Bookshelf Library - Loan letter");
headerRun.setFontSize(9);
XWPFFooter footer = document.createFooter(HeaderFooterType.DEFAULT);
XWPFParagraph footerParagraph = footer.createParagraph();
footerParagraph.setAlignment(ParagraphAlignment.CENTER);
footerParagraph.createRun().setText("Page ");
addField(footerParagraph, "PAGE", "1");
footerParagraph.createRun().setText(" of ");
addField(footerParagraph, "NUMPAGES", "1");
XWPFParagraph body = document.createParagraph();
body.createRun().setText("Dear reader, your loan of 'Dune' is due on 2026-11-03.");
try (var out = Files.newOutputStream(target)) {
document.write(out);
}
}
}
private static void addField(XWPFParagraph paragraph, String instruction, String cachedValue) {
XWPFFieldRun field = paragraph.createFieldRun();
field.setFieldInstruction(instruction);
field.setText(cachedValue);
}
}
Source: https://poi.apache.org/components/document/quick-guide-xwpf.html (header and footer section) and the Javadoc of XWPFDocument and HeaderFooterType.
A new XWPFDocument() starts with no header and no footer, so a document generated from scratch only has what you
create. Creating a header or footer also creates the section properties (w:sectPr) at the end of the body, which is
needed later in this page.
XWPFHeaderFooterPolicy
XWPFHeaderFooterPolicy (package org.apache.poi.xwpf.model) is the object that knows which header and footer parts a
section references. It is the older, lower-level API, and it is still the right tool in three situations: reading the
headers of an existing document, working on a section other than the last one, and creating a header with ready-made
paragraphs. Its type constants are the Open XML ones, STHdrFtr.DEFAULT, STHdrFtr.FIRST and STHdrFtr.EVEN, which are
also exposed as XWPFHeaderFooterPolicy.DEFAULT, FIRST and EVEN.
| Method | Purpose |
|---|---|
|
Policy for the section at the end of the body (the last section). This is what |
|
Policy for any section, identified by its |
|
Create and return the header or footer of that type for the section. Overloads accept an array of |
|
Return the header of that type, or |
|
Header or footer by page number, applying the first, even and odd rules of the policy. |
|
Adds a text watermark to the headers of the section. It is a drawing built by POI, and its rendering depends on the consumer. |
The document-level shortcuts are simply document.getHeaderList() and document.getFooterList(): every header and footer
part of the file, in package order, regardless of which section uses it.
Reading the headers and footers of the Bookshelf template, for example to check what a customer template contains:
import java.io.IOException;
import java.nio.file.Path;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFFooter;
import org.apache.poi.xwpf.usermodel.XWPFHeader;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.PackageAccess;
public class InspectHeadersAndFooters {
public static void print(Path file) throws IOException, InvalidFormatException {
try (OPCPackage pkg = OPCPackage.open(file.toFile(), PackageAccess.READ);
XWPFDocument document = new XWPFDocument(pkg)) {
for (XWPFHeader header : document.getHeaderList()) {
System.out.println("header: " + header.getText().strip());
}
for (XWPFFooter footer : document.getFooterList()) {
System.out.println("footer: " + footer.getText().strip());
}
XWPFHeaderFooterPolicy policy = document.getHeaderFooterPolicy();
if (policy != null && policy.getFirstPageHeader() != null) {
System.out.println("first-page header: " + policy.getFirstPageHeader().getText().strip());
}
}
}
}
Source: https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/model/XWPFHeaderFooterPolicy.html and the official example BetterHeaderFooterExample.
XWPFHeaderFooter.getText() joins the text of the paragraphs and tables of the part. Text produced by a field shows its
cached result, the value stored the last time Word calculated it: a page number field in a footer therefore reads as the
number that was current when the file was saved, not as a real page number.
Different first page and different odd and even pages
Both options need a switch outside the header itself:
-
Different first page is a flag of the section,
w:titlePg(the element means "on" when present). Without it, aFIRSTheader exists in the package but Word ignores it.XWPFDocument.createHeader(HeaderFooterType.FIRST)andcreateFooter(HeaderFooterType.FIRST)add it to the section of the body; withXWPFHeaderFooterPolicyon a section of your own, as in the example below, add it withsectPr.addNewTitlePg(). -
Different odd and even pages is a flag of the document:
document.getSettings().getCTSettings().addNewEvenAndOddHeaders(). Without it, theEVENheader is ignored.
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy;
import org.apache.poi.xwpf.usermodel.BreakType;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STHdrFtr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STSectionMark;
public class MonthlyReportHeaders {
public static void write(Path target) throws IOException {
try (XWPFDocument document = new XWPFDocument()) {
document.createParagraph().createRun().setText("Bookshelf monthly report - October 2026");
XWPFParagraph pageBreak = document.createParagraph();
pageBreak.createRun().addBreak(BreakType.PAGE);
document.createParagraph().createRun().setText("Loans by genre ...");
// the section that ends the body: this is the one the policy edits
CTSectPr section = document.getDocument().getBody().addNewSectPr();
section.addNewTitlePg(); // enable the FIRST header
section.addNewType().setVal(STSectionMark.NEXT_PAGE);
section.addNewPgNumType().setStart(BigInteger.ONE); // page numbers start at 1
XWPFHeaderFooterPolicy policy = new XWPFHeaderFooterPolicy(document, section);
policy.createHeader(STHdrFtr.FIRST); // empty: the cover page has no header
policy.createHeader(STHdrFtr.DEFAULT).createParagraph().createRun().setText("Monthly report - odd pages");
policy.createHeader(STHdrFtr.EVEN).createParagraph().createRun().setText("Bookshelf Library - even pages");
document.getSettings().getCTSettings().addNewEvenAndOddHeaders();
try (var out = Files.newOutputStream(target)) {
document.write(out);
}
}
}
}
Source: https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/model/XWPFHeaderFooterPolicy.html and https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/usermodel/XWPFSettings.html.
POI never lays the document out, so none of these headers is "calculated": the file only records which part belongs to which page type, and Word, LibreOffice or a viewer decides at render time.
Pictures, tables and tab stops in a header
A header is an ordinary body: header.createTable(1, 2) makes a two-cell table (a logo on the left, the title on the
right), and XWPFRun.addPicture(...) works in a header paragraph exactly as in the body, as described in
Word Tables, Lists & Images. One difference: the picture data
of a header is stored through header.addPictureData(...), and run.addPicture on a run that belongs to a header does the
right thing automatically. A common layout without a table is one paragraph with a centred and a right-aligned part
separated by run.addTab(), provided the paragraph style or the template defines the tab stops.
Fields
A field is an instruction that Word evaluates to produce text: the current page, the number of pages, today’s date, the text of a heading, a table of contents. The file stores both the instruction and the last result, called the cached result or field result. Two different encodings exist:
-
Simple field,
w:fldSimple w:instr="PAGE", with the cached result as the child runs. Compact, and enough for everything that does not span paragraphs. -
Complex field, a run sequence
fldChar begin,instrText,fldChar separate, the result runs andfldChar end. Required for fields whose instruction or result contains formatting, other fields or several paragraphs, and what Word itself writes for a table of contents.
Page numbers with XWPFFieldRun
XWPFParagraph.createFieldRun() (and insertNewFieldRun(int)) creates a w:fldSimple with a run inside and returns an
XWPFFieldRun, a subclass of XWPFRun with two extra methods, setFieldInstruction(String) and getFieldInstruction().
Text and formatting set on the field run (setText, setBold, setFontSize) apply to the cached result. The code of the
loan letter above produced this footer XML, which is what Word reads:
<w:ftr>
<w:p>
<w:pPr><w:jc w:val="center"/></w:pPr>
<w:r><w:t xml:space="preserve">Page </w:t></w:r>
<w:fldSimple w:instr="PAGE"><w:r><w:t>1</w:t></w:r></w:fldSimple>
<w:r><w:t xml:space="preserve"> of </w:t></w:r>
<w:fldSimple w:instr="NUMPAGES"><w:r><w:t>1</w:t></w:r></w:fldSimple>
</w:p>
</w:ftr>
The instructions most useful in a Bookshelf report:
| Instruction | Result | Typical place |
|---|---|---|
|
Number of the current page |
Footer |
|
Total number of pages in the document |
Footer ("Page 2 of 7") |
|
Number of pages of the current section |
Footer of a chapter with restarted numbering |
|
Date or time at the moment of rendering |
Header of a report, with a switch such as |
|
The document property of that name |
Header |
|
Text of the nearest heading with that style |
Running chapter title in a header |
|
A table of contents (see below) |
Body |
Because the cached result in the file is whatever you wrote, three behaviours are worth knowing:
-
Word and LibreOffice recalculate
PAGE,NUMPAGESandDATEwhen they render or print. That is why the cached "1" is harmless in a header or footer. -
Other readers do not. A text extractor, a preview in a mail client or a converter that does not evaluate fields shows the cached text. Write a sensible cached result.
-
POI does not calculate fields. There is no method that computes the page number or the page count, because POI has no layout engine.
A complex field when the simple one is not enough
A field that must carry formatting in its result, or that is a date with a format switch, is more robust as the full
run sequence. The beans CTR, CTFldChar and CTText are used through XWPFRun.getCTR(). The helper below creates the
five-part sequence and is reused by the examples after it.
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType;
public final class Fields {
private Fields() {
}
/** Appends a complex field such as +DATE \@ "yyyy-MM-dd"+ with a cached result. */
public static void addComplexField(XWPFParagraph paragraph, String instruction, String cachedResult) {
XWPFRun begin = paragraph.createRun();
begin.getCTR().addNewFldChar().setFldCharType(STFldCharType.BEGIN);
XWPFRun instr = paragraph.createRun();
instr.getCTR().addNewInstrText().setStringValue(" " + instruction + " ");
XWPFRun separate = paragraph.createRun();
separate.getCTR().addNewFldChar().setFldCharType(STFldCharType.SEPARATE);
paragraph.createRun().setText(cachedResult);
XWPFRun end = paragraph.createRun();
end.getCTR().addNewFldChar().setFldCharType(STFldCharType.END);
}
}
Source: https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/usermodel/XWPFFieldRun.html and the field definitions in ECMA-376 part 1 (17.16, fields and hyperlinks), https://ecma-international.org/publications-and-standards/standards/ecma-376/.
Simple fields can also be read: the runs of a paragraph include XWPFFieldRun objects for w:fldSimple, and the
getFieldInstruction() of each tells which field it is. Complex fields are plain runs with fldChar children. Placeholder replacement that ignores fields can destroy them, a
point taken up in
Word Templates & Placeholder Replacement.
Sections and page setup
A section is a run of pages that share the same page size, orientation, margins, columns, header and footer, and page
numbering. Every document has at least one. Page setup is not stored in the body or in a style, but in the element
w:sectPr ("section properties"), and XWPF has no high-level class for it: you use the generated bean CTSectPr and its
children.
Where the section properties are
The position of the w:sectPr determines which pages it describes, and it is the single most confusing rule of the
format:
-
The
w:sectPrthat is a direct child ofw:body, at its end, describes the last section. -
The
w:sectProf every earlier section is stored inside the properties of the last paragraph of that section (w:p/w:pPr/w:sectPr). That paragraph is the section break.
w:pPr/w:sectPr: portrait A4, header A"] p2["paragraphs of section 2 (landscape)"] sect["w:sectPr at the end of w:body:
landscape A4, header B"] p1 --> p1b --> p2 --> sect
In XWPF, the two ways to obtain a CTSectPr are therefore:
// the last section (end of the body): create it when a new document has none yet
CTSectPr lastSection = document.getDocument().getBody().isSetSectPr()
? document.getDocument().getBody().getSectPr()
: document.getDocument().getBody().addNewSectPr();
// a section break after a paragraph: that paragraph ends the section
CTSectPr earlierSection = paragraph.getCTP().addNewPPr().addNewSectPr();
A new XWPFDocument() has no w:sectPr until a header, a footer or the code creates one, and a document written by Word
always has one.
Page size, margins and orientation
Page measures in WordprocessingML are in twips: one twentieth of a point, so 1440 twips are one inch and about 567 are a centimetre. An A4 page is 11906 x 16838 twips and US Letter is 12240 x 15840. The three elements used for page setup:
| Element (bean method) | Attributes |
|---|---|
|
|
|
|
|
|
Two details cause most of the bugs. First, the orientation attribute does not swap the dimensions: Word draws the page
from w and h, and orient="landscape" is only a hint for printers and dialogs. For a landscape page you must set the
width larger than the height and the attribute. Second, the numeric attributes take a BigInteger in the generated
beans of POI 5.5.1, so keep this code in one helper, as the example below does.
The following builds a Bookshelf monthly-report.docx with a portrait summary section and a landscape section for a wide
table, each with its own header.
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageMar;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageSz;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STHdrFtr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STPageOrientation;
public class MonthlyReportSections {
private static final long A4_SHORT = 11906;
private static final long A4_LONG = 16838;
private static final long ONE_INCH = 1440;
public static void write(Path target) throws IOException {
try (XWPFDocument document = new XWPFDocument()) {
// section 1: portrait summary, its properties go into its last paragraph
document.createParagraph().createRun().setText("Summary of the month");
XWPFParagraph lastOfSection1 = document.createParagraph();
lastOfSection1.createRun().setText("Total loans: 412");
CTSectPr portrait = lastOfSection1.getCTP().addNewPPr().addNewSectPr();
pageSetup(portrait, A4_SHORT, A4_LONG, false);
new XWPFHeaderFooterPolicy(document, portrait)
.createHeader(STHdrFtr.DEFAULT)
.createParagraph().createRun().setText("Monthly report - summary");
// section 2: landscape, the section properties of the last section go into the body
document.createParagraph().createRun().setText("Loans per branch and genre (wide table)");
CTSectPr landscape = document.getDocument().getBody().addNewSectPr();
pageSetup(landscape, A4_LONG, A4_SHORT, true);
new XWPFHeaderFooterPolicy(document, landscape)
.createHeader(STHdrFtr.DEFAULT)
.createParagraph().createRun().setText("Monthly report - details");
try (var out = Files.newOutputStream(target)) {
document.write(out);
}
}
}
private static void pageSetup(CTSectPr section, long width, long height, boolean landscape) {
CTPageSz size = section.addNewPgSz();
size.setW(BigInteger.valueOf(width));
size.setH(BigInteger.valueOf(height));
if (landscape) {
size.setOrient(STPageOrientation.LANDSCAPE);
}
CTPageMar margins = section.addNewPgMar();
margins.setTop(BigInteger.valueOf(ONE_INCH));
margins.setBottom(BigInteger.valueOf(ONE_INCH));
margins.setLeft(BigInteger.valueOf(ONE_INCH));
margins.setRight(BigInteger.valueOf(ONE_INCH));
margins.setHeader(BigInteger.valueOf(708));
margins.setFooter(BigInteger.valueOf(708));
}
}
Source: https://poi.apache.org/components/document/quick-guide-xwpf.html and the schema of w:sectPr in ECMA-376 part 1
(17.6, sections), https://ecma-international.org/publications-and-standards/standards/ecma-376/.
The generated document.xml of this program is the structure of the second diagram above:
<w:p>
<w:pPr>
<w:sectPr>
<w:headerReference w:type="default" r:id="rId2"/>
<w:pgSz w:w="11906" w:h="16838"/>
<w:pgMar w:top="1440" w:bottom="1440" w:left="1440" w:right="1440" w:header="708" w:footer="708"/>
</w:sectPr>
</w:pPr>
<w:r><w:t>Total loans: 412</w:t></w:r>
</w:p>
...
<w:sectPr>
<w:headerReference w:type="default" r:id="rId3"/>
<w:pgSz w:w="16838" w:h="11906" w:orient="landscape"/>
</w:sectPr>
Section break types, numbering and columns
The element w:type of the section properties says how the section starts, and STSectionMark has the values
NEXT_PAGE (the default), CONTINUOUS, EVEN_PAGE, ODD_PAGE and NEXT_COLUMN. A continuous break lets a section with
two text columns follow one with a single column on the same page; two columns are set with
section.addNewCols().setNum(BigInteger.valueOf(2)). pgNumType restarts the page number of the section, as the monthly
report example above does for the cover.
A section without its own headerReference inherits the header of the previous section. A new section therefore shows
the same header and footer as the one before it until you create its own, and editing the shared header changes both
sections. If you want an independent header, create it for the new section’s CTSectPr explicitly, as shown above.
What POI does not provide for sections: there is no XWPFSection class and no helper that sets A4 or Letter, margins in
centimetres or an orientation switch. The code above is the intended approach and is also what the official examples do.
Page breaks
There are four ways to start a new page, and they differ in what they say about the document structure.
| Method | Stored as | Use when |
|---|---|---|
|
A |
Explicit break at an exact point of the text |
|
|
The paragraph always starts on a new page, even if the text before it changes; the right choice for chapter titles |
|
|
Jump to the next text column |
A |
A section break |
The next page also has other page setup or another header |
BreakType.TEXT_WRAPPING is the plain line break (Shift+Enter in Word). An empty paragraph with a page break works, but
it leaves an empty line at the end of the previous page and can create an unwanted blank page when the page was already
full; setPageBreak(true) on the first paragraph of the next chapter avoids that.
XWPFParagraph chapterTitle = document.createParagraph();
chapterTitle.setStyle("Heading1");
chapterTitle.setPageBreak(true); // always starts on a new page
chapterTitle.createRun().setText("Overdue loans");
XWPFRun run = document.createParagraph().createRun();
run.setText("End of the summary.");
run.addBreak(BreakType.PAGE); // explicit break after this text
Table of contents
A table of contents (TOC) is a field, TOC \o "1-3" \h \z \u, whose result is a list of the headings with page numbers.
The headings are paragraphs whose style is Heading1, Heading2 and so on (the style IDs of Word’s built-in styles). POI
offers two ways to produce one, and both have limits that you should know before promising a table of contents to users.
document.createTOC()
XWPFDocument.createTOC() builds a TOC block (a structured document tag, w:sdt, of the gallery "Table of contents") from
the headings that exist in the document at the moment of the call. It adds a title paragraph and, for every paragraph
that has a heading style, an entry paragraph with the style TOC1, TOC2 and so on, the heading text, a dotted tab leader
and a PAGEREF field. The experiment below, with POI 5.5.1, shows both its use and its limits.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
public class MonthlyReportWithToc {
public static void write(Path target) throws IOException {
try (XWPFDocument document = new XWPFDocument()) {
addHeading(document, "Overdue loans");
addHeading(document, "Most borrowed books");
addHeading(document, "New acquisitions");
document.createTOC(); // must come after the headings it lists
document.enforceUpdateFields(); // ask Word to refresh the numbers when the file opens
try (var out = Files.newOutputStream(target)) {
document.write(out);
}
}
}
private static void addHeading(XWPFDocument document, String text) {
XWPFParagraph heading = document.createParagraph();
heading.setStyle("Heading1");
heading.setPageBreak(true);
heading.createRun().setText(text);
}
}
Source: https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/usermodel/XWPFDocument.html and the class TOC.
What this really produces, and what it does not:
-
The TOC is appended at the end of the body, not where you called it from the reading order of your code, because
createTOCadds a new body element. To put it at the start, move the generatedw:sdtwith anXmlCursor, or generate the document from a template whose TOC is already in place. -
Only the headings that exist at the time of the call are listed. Headings added later are missing until Word updates the field.
-
The page numbers are placeholders (the generated entries show page "1"). POI cannot know on which page a heading will land, because it does not paginate.
-
The styles must exist. A document made with
new XWPFDocument()has nostyles.xml, soHeading1,TOC1andTOCHeadingare only names. Word falls back to defaults and the result looks plain. Start from a template that defines these styles, or create them withXWPFStylesas described in Word Documents with XWPF.
enforceUpdateFields
Since the cached numbers cannot be correct, the practical recipe is to ask the consumer to recalculate them.
XWPFDocument.enforceUpdateFields() (equivalent to document.getSettings().setUpdateFields()) writes
<w:updateFields w:val="on"/> in settings.xml. When Word opens the file it shows a dialog asking whether to update the
fields in the document, and on "Yes" refreshes the table of contents, the page references and the other fields. The
getter is isEnforcedUpdateFields().
Three consequences of this design:
-
The dialog is shown to every reader who opens the file in Word, which is acceptable for a report a librarian opens once and unfriendly for a document sent to customers.
-
Other software (a PDF converter, a web preview, LibreOffice in some versions) may ignore the flag, so the numbers stay as the placeholders.
-
Opening the file and saving it in Word makes the table of contents permanent and removes the need for the flag.
The alternative for server-generated reports that must look right without a prompt is to keep the table of contents in a template that was saved by Word with correct numbers, and to produce the report by replacing content. That trade-off is the main reason the tables of contents in the Bookshelf monthly report come from a template, as described in Word Templates & Placeholder Replacement.
Footnotes and endnotes
Footnotes appear at the bottom of the page where they are referenced, endnotes at the end of the document or section. They
are stored in two optional parts, footnotes.xml and endnotes.xml, each holding notes identified by a numeric ID, with
the two reserved notes (separator and continuation separator) that Word needs. In the body, a run contains a
w:footnoteReference or w:endnoteReference with the ID.
XWPF has real, usable support for footnotes: XWPFDocument.createFootnotes() creates the part,
XWPFFootnotes.createFootnote() adds a note whose content is an ordinary list of paragraphs
(XWPFFootnote extends XWPFAbstractFootnoteEndnote, an IBody), and
XWPFParagraph.addFootnoteReference(XWPFAbstractFootnoteEndnote) inserts the reference run with the style
FootnoteReference.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFFootnote;
import org.apache.poi.xwpf.usermodel.XWPFFootnotes;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
public class LoanLetterWithFootnote {
public static void write(Path target) throws IOException {
try (XWPFDocument document = new XWPFDocument()) {
XWPFFootnotes footnotes = document.createFootnotes();
XWPFParagraph paragraph = document.createParagraph();
paragraph.createRun().setText("The late fee is 0.25 EUR per day");
XWPFFootnote note = footnotes.createFootnote();
note.createParagraph().createRun().setText("Fees are capped at the price of the book.");
paragraph.addFootnoteReference(note); // inserts the numbered reference mark
paragraph.createRun().setText(".");
try (var out = Files.newOutputStream(target)) {
document.write(out);
}
}
}
}
Source: https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/usermodel/XWPFFootnotes.html and https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/usermodel/XWPFFootnote.html.
Reading is symmetric: document.getFootnotes() returns all notes (including the two separator notes that carry ID -1
and 0 in files written by Word), getFootnoteByID(int) finds one, and XWPFParagraph.getFootnoteText() returns the text of
the notes referenced from that paragraph.
Endnotes are the same model, with XWPFEndnote and getEndnoteByID(int). XWPFDocument.createEndnotes() creates
the part, XWPFEndnotes.createEndnote() (or XWPFDocument.createEndnote(), since 4.0.0, which does both) adds a note,
and the same addFootnoteReference writes a w:endnoteReference when it is given an XWPFEndnote. The
addEndnote(CTFtnEdn) methods that take a bean are marked @Internal; do not use them.
The placement, the numbering format (Arabic, roman, symbols) and the restart rule of notes are properties of the section
(w:footnotePr, w:endnotePr) and the document settings, and are not exposed by a high-level method. The reference mark
looks right only when the style FootnoteReference (superscript) and FootnoteText exist, so again a template with
styles gives the result users expect.
Comments
Review comments have a small public API in XWPF. In 5.5.1:
-
document.createComments()createscomments.xmland returns anXWPFComments. -
comments.createComment(BigInteger id)adds anXWPFCommentwith that ID. It is anIBody, so its text is added withcomment.createParagraph().createRun().setText(...).setAuthor,setInitialsandsetDate(Calendar)set the attributes Word shows in the margin. -
The comment is anchored in the body by three markers with the same ID:
commentRangeStartbefore the commented text,commentRangeEndafter it, and a run containing acommentReference. POI does not set the markers for you; they are added through theCTPandCTRbeans.
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Calendar;
import org.apache.poi.xwpf.usermodel.XWPFComment;
import org.apache.poi.xwpf.usermodel.XWPFComments;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
public class LoanLetterWithComment {
public static void write(Path target) throws IOException {
try (XWPFDocument document = new XWPFDocument()) {
XWPFComments comments = document.createComments();
BigInteger id = BigInteger.ZERO;
XWPFComment comment = comments.createComment(id);
comment.setAuthor("Librarian");
comment.setInitials("LB");
comment.setDate(Calendar.getInstance());
comment.createParagraph().createRun().setText("Check the due date with the reader.");
XWPFParagraph paragraph = document.createParagraph();
paragraph.getCTP().addNewCommentRangeStart().setId(id);
paragraph.createRun().setText("Due date: 2026-11-03");
paragraph.getCTP().addNewCommentRangeEnd().setId(id);
paragraph.createRun().getCTR().addNewCommentReference().setId(id);
try (var out = Files.newOutputStream(target)) {
document.write(out);
}
}
}
}
Source: https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/usermodel/XWPFComments.html and https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/usermodel/XWPFComment.html.
Each comment ID must be unique in the document and the same in the three markers; a missing commentReference makes the
comment invisible, and a duplicate ID makes Word report a corrupt file. To read the comments of an uploaded document use
document.getComments() (an array) or document.getDocComments().getComments(). Comment replies and resolved state
(the commentsExtended.xml part) and tracked changes (w:ins, w:del) have no high-level API.
Document protection
A .docx can ask Word to restrict editing. The restriction is stored in settings.xml as w:documentProtection, and
XWPF exposes it on the document:
| Method | Effect in Word |
|---|---|
|
Read only: no editing |
|
Only form fields and content controls can be filled in |
|
Only comments can be added |
|
Every edit is recorded as a tracked change |
|
Removes the restriction (any type) |
|
Query the current state |
|
Checks a password against the stored hash |
Each enforce… method has an overload that takes a password and a HashAlgorithm
(enforceReadonlyProtection(String password, HashAlgorithm algorithm)), which stores a salted hash instead of leaving
the restriction open. The password must come from configuration or a secret store, never from source code:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.poi.poifs.crypt.HashAlgorithm;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
public class ProtectedLoanLetter {
/** The password comes from configuration, e.g. an environment variable injected by the platform. */
public static void write(Path target, String protectionPassword) throws IOException {
try (XWPFDocument document = new XWPFDocument()) {
document.createParagraph().createRun().setText("This letter is a read-only copy.");
document.enforceReadonlyProtection(protectionPassword, HashAlgorithm.sha512);
try (var out = Files.newOutputStream(target)) {
document.write(out);
}
}
}
}
Source: https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/usermodel/XWPFDocument.html and https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/usermodel/XWPFSettings.html.
The honest summary of what this protection is worth: it is a courtesy flag, not security. The text of the document is
stored unencrypted in the package, anybody can remove the w:documentProtection element from settings.xml with an
archive tool, and Word’s "Restrict Editing" only controls the Word user interface. When the content must be confidential,
encrypt the whole file with the Agile encryption of POI (EncryptionInfo, Encryptor), covered in
Encryption, Signing & Protection; when it must be unchangeable, deliver a
PDF (see the closing section).
Putting it together: the Bookshelf loan letter
The class below produces a finished loan-letter.docx with the features that matter in practice: a header with the
library name, a footer with "Page X of Y", an A4 page with 2.5 cm margins, a footnote with the fee rule, and a read-only
flag. It is a good model for a service in a Spring Boot application, where the document is built into a
ByteArrayOutputStream (small documents) or written to a temporary file (large ones) and then returned by the controller.
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import org.apache.poi.wp.usermodel.HeaderFooterType;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFFieldRun;
import org.apache.poi.xwpf.usermodel.XWPFFooter;
import org.apache.poi.xwpf.usermodel.XWPFFootnote;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageMar;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageSz;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr;
public class LoanLetterService {
private static final BigInteger MARGIN_2_5_CM = BigInteger.valueOf(1417); // 2.5 cm in twips
public byte[] createLetter(String reader, String title, LocalDate dueDate) throws IOException {
try (XWPFDocument document = new XWPFDocument();
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
// header and footer create the section properties of the body
XWPFRun headerRun = document.createHeader(HeaderFooterType.DEFAULT)
.createParagraph().createRun();
headerRun.setText("Bookshelf Library");
headerRun.setBold(true);
XWPFFooter footer = document.createFooter(HeaderFooterType.DEFAULT);
XWPFParagraph footerParagraph = footer.createParagraph();
footerParagraph.setAlignment(ParagraphAlignment.CENTER);
footerParagraph.createRun().setText("Page ");
field(footerParagraph, "PAGE");
footerParagraph.createRun().setText(" of ");
field(footerParagraph, "NUMPAGES");
// page setup on the existing section of the body
CTSectPr section = document.getDocument().getBody().getSectPr();
CTPageSz size = section.addNewPgSz();
size.setW(BigInteger.valueOf(11906));
size.setH(BigInteger.valueOf(16838));
CTPageMar margins = section.addNewPgMar();
margins.setTop(MARGIN_2_5_CM);
margins.setBottom(MARGIN_2_5_CM);
margins.setLeft(MARGIN_2_5_CM);
margins.setRight(MARGIN_2_5_CM);
margins.setHeader(BigInteger.valueOf(708));
margins.setFooter(BigInteger.valueOf(708));
// body with a footnote
XWPFParagraph greeting = document.createParagraph();
greeting.createRun().setText("Dear " + reader + ",");
XWPFParagraph text = document.createParagraph();
text.createRun().setText("Your loan of “" + title + "” is due on "
+ dueDate.format(DateTimeFormatter.ISO_LOCAL_DATE) + ". A late fee applies afterwards");
XWPFFootnote note = document.createFootnotes().createFootnote();
note.createParagraph().createRun().setText("0.25 EUR per day, capped at the price of the book.");
text.addFootnoteReference(note);
text.createRun().setText(".");
document.write(out);
return out.toByteArray();
}
}
private static void field(XWPFParagraph paragraph, String instruction) {
XWPFFieldRun field = paragraph.createFieldRun();
field.setFieldInstruction(instruction);
field.setText("1");
}
}
In the example the getSectPr() call relies on the fact that createHeader and createFooter already created the
w:sectPr of the body. Calling it on a document without header or footer returns null; the safe form is the
isSetSectPr() ? getSectPr() : addNewSectPr() pattern shown earlier. Also note that the ByteArrayOutputStream is only
suitable for documents of a few hundred kilobytes: XWPF holds the entire document model in memory, and there is no
streaming writer for Word, as explained in
Word Documents with XWPF.
Pitfalls
| Symptom | Cause and remedy |
|---|---|
The first-page or even-page header never shows |
The |
A landscape page prints as portrait |
The width and height were not swapped. Set |
Changing the header of one section changes them all |
A section without its own |
|
A new document has no section properties and no policy until a header or footer is created. Check |
The footer says "Page 1 of 1" in a viewer |
The viewer does not evaluate fields and shows the cached result. Word and LibreOffice recalculate when they render. |
The table of contents shows page 1 for everything, or an empty list |
POI cannot paginate; |
The heading looks like normal text |
The style IDs |
Word reports the file as corrupt after adding comments |
Duplicate or missing comment IDs, or a |
Placeholder replacement removed the page number |
The replacement code rewrote runs inside a field. Skip field runs, or replace only runs that are not |
Header text appears twice |
Text was added to a header that already contained a paragraph, typically one read from a template. Reuse
|
What POI does not provide here
-
No layout and no pagination. POI cannot tell how many pages a document has, on which page a heading falls, or whether a table fits. Page numbers, the page count,
SECTIONPAGESand the table of contents are therefore always the cached values written by whoever saved the file last. -
No high-level section API. There is no
XWPFSection; page size, margins, orientation, columns, page numbering, footnote numbering and the section type all require theCTSectPrbeans. -
No automatic table of contents.
createTOC()is a basic generator with placeholder page numbers, appended at the end of the body; a Word-quality TOC needs Word (or LibreOffice) to update the field. -
No evaluation of fields. POI writes and reads field instructions, but never computes
DATE,PAGE,STYLEREFor a cross reference. -
Limited review features. Comments have a basic API; replies, resolved state, tracked changes, and the revision history have none.
-
Protection is not security. The document protection flag is a Word user-interface setting; use file encryption for confidentiality.
-
No PDF output. POI does not render
.docxto PDF, so a report that must be tamper-proof is produced through another tool, see the PDF options in the section PDF Generation. -
No XWPF streaming. Unlike SXSSF for Excel, the whole document is in memory; thousands of pages are better produced as several documents.
Changes between versions
| Version | Relevant change for this page |
|---|---|
4.x to 5.0 |
The OOXML schema beans (the |
5.x |
The public API for footnotes ( |
6.0 (in development) |
Removal of deprecated APIs is announced. Keep the use of the |
References
-
Document components (HWPF and XWPF): https://poi.apache.org/components/document/
-
XWPF quick guide: https://poi.apache.org/components/document/quick-guide-xwpf.html
-
Document overview: https://poi.apache.org/components/document/docoverview.html
-
Javadoc,
XWPFDocument: https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/usermodel/XWPFDocument.html -
Javadoc,
XWPFHeaderFooterPolicy: https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/model/XWPFHeaderFooterPolicy.html -
Javadoc,
HeaderFooterType: https://poi.apache.org/apidocs/dev/org/apache/poi/wp/usermodel/HeaderFooterType.html -
Javadoc,
XWPFFieldRun: https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/usermodel/XWPFFieldRun.html -
Javadoc,
XWPFFootnotes: https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/usermodel/XWPFFootnotes.html -
Javadoc,
XWPFComments: https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/usermodel/XWPFComments.html -
Javadoc,
XWPFSettings: https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/usermodel/XWPFSettings.html -
Official examples,
xwpf/usermodel(includingBetterHeaderFooterExample): https://github.com/apache/poi/tree/trunk/poi-examples/src/main/java/org/apache/poi/examples/xwpf/usermodel/ -
Standard ECMA-376 (WordprocessingML, sections, fields): https://ecma-international.org/publications-and-standards/standards/ecma-376/
-
Release changes: https://poi.apache.org/changes.html