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

/word/header1.xml, header2.xml … (one part per header)

XWPFDocument.createHeader, getHeaderList, XWPFHeaderFooterPolicy

Footer

/word/footer1.xml, footer2.xml …

XWPFDocument.createFooter, getFooterList, XWPFHeaderFooterPolicy

Section (page size, margins, orientation, which header applies)

An element w:sectPr inside /word/document.xml: in the last paragraph of each section, and one at the end of the body for the last section

CTSectPr (low-level bean), XWPFHeaderFooterPolicy(document, sectPr)

Footnotes and endnotes

/word/footnotes.xml, /word/endnotes.xml

XWPFFootnotes, XWPFFootnote, XWPFEndnote

Comments

/word/comments.xml

XWPFComments, XWPFComment

Fields (page number, date, table of contents)

w:fldSimple or the w:fldChar / w:instrText run sequence, inside any paragraph of the body, a header or a footer

XWPFParagraph.createFieldRun, XWPFFieldRun

Document settings and protection

/word/settings.xml

XWPFSettings (via getSettings), XWPFDocument.enforce…Protection, enforceUpdateFields

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.

flowchart LR DOC["/word/document.xml
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

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

DEFAULT

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.

FIRST

The first page of the section, but only when the section has the "different first page" option (w:titlePg) set. createHeader(HeaderFooterType.FIRST) and createFooter(HeaderFooterType.FIRST) set it in the section of the body themselves. Typical use: a cover page without a running header.

EVEN

Even-numbered pages, but only when the document has the "different odd and even pages" option (w:evenAndOddHeaders in settings.xml) set; no POI method sets it for you (see below). DEFAULT then applies to the odd pages.

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

new XWPFHeaderFooterPolicy(document)

Policy for the section at the end of the body (the last section). This is what document.getHeaderFooterPolicy() returns for a document that was read from a file; it returns null for a new, empty document that has no header or footer yet.

new XWPFHeaderFooterPolicy(document, sectPr)

Policy for any section, identified by its CTSectPr. This is the way to give a middle section its own header.

createHeader(STHdrFtr.Enum), createFooter(STHdrFtr.Enum)

Create and return the header or footer of that type for the section. Overloads accept an array of XWPFParagraph to use as the content.

getDefaultHeader(), getFirstPageHeader(), getEvenPageHeader(), getOddPageHeader()

Return the header of that type, or null when the section has none. The same four exist for footers.

getHeader(int), getFooter(int)

Header or footer by page number, applying the first, even and odd rules of the policy.

createWatermark(String)

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());
            }
        }
    }
}

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, a FIRST header exists in the package but Word ignores it. XWPFDocument.createHeader(HeaderFooterType.FIRST) and createFooter(HeaderFooterType.FIRST) add it to the section of the body; with XWPFHeaderFooterPolicy on a section of your own, as in the example below, add it with sectPr.addNewTitlePg().

  • Different odd and even pages is a flag of the document: document.getSettings().getCTSettings().addNewEvenAndOddHeaders(). Without it, the EVEN header 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);
            }
        }
    }
}

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 and fldChar 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

PAGE

Number of the current page

Footer

NUMPAGES

Total number of pages in the document

Footer ("Page 2 of 7")

SECTIONPAGES

Number of pages of the current section

Footer of a chapter with restarted numbering

DATE, TIME

Date or time at the moment of rendering

Header of a report, with a switch such as DATE \@ "yyyy-MM-dd"

TITLE, AUTHOR

The document property of that name

Header

STYLEREF "Heading 1"

Text of the nearest heading with that style

Running chapter title in a header

TOC \o "1-3" \h \z \u

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, NUMPAGES and DATE when 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);
    }
}

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:sectPr that is a direct child of w:body, at its end, describes the last section.

  • The w:sectPr of 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.

flowchart TB p1["paragraphs of section 1 (portrait)"] p1b["last paragraph of section 1
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

pgSz (sectPr.addNewPgSz())

w and h (page width and height, twips), orient (STPageOrientation.PORTRAIT or LANDSCAPE)

pgMar (sectPr.addNewPgMar())

top, bottom, left, right, header and footer (distance from the page edge to the header or footer), gutter

pgNumType (sectPr.addNewPgNumType())

start (restart numbering at this number), fmt (number format such as roman numerals)

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));
    }
}

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

run.addBreak(BreakType.PAGE)

A w:br w:type="page" inside a run

Explicit break at an exact point of the text

paragraph.setPageBreak(true)

w:pageBreakBefore in the paragraph properties

The paragraph always starts on a new page, even if the text before it changes; the right choice for chapter titles

run.addBreak(BreakType.COLUMN)

w:br w:type="column"

Jump to the next text column

A w:sectPr with w:type set to NEXT_PAGE

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);
    }
}

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 createTOC adds a new body element. To put it at the start, move the generated w:sdt with an XmlCursor, 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 no styles.xml, so Heading1, TOC1 and TOCHeading are only names. Word falls back to defaults and the result looks plain. Start from a template that defines these styles, or create them with XWPFStyles as 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);
            }
        }
    }
}

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:

  1. document.createComments() creates comments.xml and returns an XWPFComments.

  2. comments.createComment(BigInteger id) adds an XWPFComment with that ID. It is an IBody, so its text is added with comment.createParagraph().createRun().setText(...). setAuthor, setInitials and setDate(Calendar) set the attributes Word shows in the margin.

  3. The comment is anchored in the body by three markers with the same ID: commentRangeStart before the commented text, commentRangeEnd after it, and a run containing a commentReference. POI does not set the markers for you; they are added through the CTP and CTR beans.

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);
            }
        }
    }
}

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

enforceReadonlyProtection()

Read only: no editing

enforceFillingFormsProtection()

Only form fields and content controls can be filled in

enforceCommentsProtection()

Only comments can be added

enforceTrackedChangesProtection()

Every edit is recorded as a tracked change

removeProtectionEnforcement()

Removes the restriction (any type)

isEnforcedProtection(), isEnforcedReadonlyProtection(), isEnforcedFillingFormsProtection(), isEnforcedCommentsProtection(), isEnforcedTrackedChangesProtection()

Query the current state

validateProtectionPassword(String)

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);
            }
        }
    }
}

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 FIRST header needs titlePg in the section (createHeader(HeaderFooterType.FIRST) adds it, XWPFHeaderFooterPolicy does not); the EVEN header needs evenAndOddHeaders in the settings, which only the settings bean sets. Creating the part is not enough.

A landscape page prints as portrait

The width and height were not swapped. Set w larger than h and orient="landscape".

Changing the header of one section changes them all

A section without its own headerReference inherits the previous one. Create a header for each section’s CTSectPr.

NullPointerException on getSectPr() or getHeaderFooterPolicy()

A new document has no section properties and no policy until a header or footer is created. Check isSetSectPr() or call createHeaderFooterPolicy().

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; createTOC() only lists headings that already exist and writes placeholders. Use enforceUpdateFields() or a template saved by Word.

The heading looks like normal text

The style IDs Heading1, TOC1, FootnoteReference are not defined in a document created with new XWPFDocument(). Start from a template or create the styles.

Word reports the file as corrupt after adding comments

Duplicate or missing comment IDs, or a commentRangeStart without matching commentRangeEnd.

Placeholder replacement removed the page number

The replacement code rewrote runs inside a field. Skip field runs, or replace only runs that are not XWPFFieldRun.

Header text appears twice

Text was added to a header that already contained a paragraph, typically one read from a template. Reuse getParagraphs().get(0) instead of calling createParagraph() when the part is not empty. (In 5.5.1 a header made by createHeader starts with no paragraph, as the footer XML above shows, so createParagraph() is right for it.)

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, SECTIONPAGES and 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 the CTSectPr beans.

  • 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, STYLEREF or 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 .docx to 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 CT* classes) are split between poi-ooxml-lite, a subset used by POI itself, and poi-ooxml-full. The beans used on this page (CTSectPr, CTPageSz, CTPageMar, CTFldChar) compiled and ran against the lite jar in the tests of this page, but a bean that is not in the lite jar fails at run time with a NoClassDefFoundError; see Setup & Dependencies for the fix.

5.x

The public API for footnotes (XWPFFootnotes, createFootnotes), comments (createComments, XWPFComment) and field runs (XWPFFieldRun) is the one of 5.5.1 described here; older 5.x releases may lack some of these methods, so check the Javadoc of the exact version you use.

6.0 (in development)

Removal of deprecated APIs is announced. Keep the use of the CT* beans in a few helper classes (like pageSetup above), because that is where generated numeric types or schema versions are most likely to change. Check the change log when 6.0 ships.