Setup & Dependencies

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.

Before any Bookshelf workbook or letter can be generated, the right POI jars must be on the classpath, and they must agree with each other. Most problems reported against POI are not API problems but classpath problems: a missing schema jar, two POI versions side by side, or a logging message that nobody can see. This page lists the artifacts, shows how to declare them with Maven and Gradle, and explains the transitive dependencies, the logging setup and the module-system details.

Versions are stated as of 2026-10-09: Apache POI 5.5.1 (released 30 November 2025; the 6.0 line has not been released), Apache XMLBeans 5.4.1 on Maven Central (POI 5.5.1 itself declares 5.3.0), Spring Boot 4.1.1 on Spring Framework 7 and Java 21. POI 5.5.x runs on Java 8 and later; the announced POI 6.0 needs JDK 17. The architecture behind the jars is explained in Introduction & Architecture, and the formats they read and write in File Formats: OOXML & OLE2.

Which artifact for which format

POI is split into several jars, all under the group id org.apache.poi. Pick them by file format, not by habit.

Artifact Contains Needed for

poi

POIFS (OLE2 container), HPSF (document properties), HSSF (.xls), DDF and the common ss.usermodel interfaces.

Anything binary: .xls workbooks, OLE2 encryption, document properties. Also pulled in by poi-ooxml.

poi-ooxml

OpenXML4J (the OPC package layer), XSSF (.xlsx), SXSSF (streaming .xlsx writer), XWPF (.docx), XSLF, XDGF and the WorkbookFactory.

Every .xlsx and .docx example of this section.

poi-ooxml-lite

The subset of the compiled OOXML schemas (XMLBeans classes in org.openxmlformats.schemas) that the POI tests use; about 6 MB (5,995,475 bytes in 5.5.1).

Pulled in automatically by poi-ooxml. Enough for typical spreadsheet and document work.

poi-ooxml-full

Every OOXML schema class; about 14 MB in 5.5.1 (the POI FAQ says "around 16mb").

Digital signatures and any low-level CT* bean that the lite jar does not contain.

poi-scratchpad

HWPF (.doc), HSLF (.ppt), HSMF (.msg), HDGF (Visio) and HPBF (Publisher).

Reading old .doc files and the other legacy formats; not needed for .xls, .xlsx or .docx.

The rule of thumb is: a Bookshelf export with books-inventory.xlsx or loan-letter.docx needs only poi-ooxml. Add poi-scratchpad only when users still upload .doc files, and swap lite for full only when a NoClassDefFoundError tells you to (see below).

Maven

Declare a single property for the POI version and use it for every POI artifact. There is no poi-bom, so the property is the way to keep versions aligned.

<properties>
    <java.version>21</java.version>
    <poi.version>5.5.1</poi.version>
</properties>

<dependencies>
    <!-- .xlsx (XSSF, SXSSF) and .docx (XWPF); brings poi and poi-ooxml-lite -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>${poi.version}</version>
    </dependency>

    <!-- Optional: legacy .doc, .ppt and .msg -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-scratchpad</artifactId>
        <version>${poi.version}</version>
    </dependency>
</dependencies>

Declaring poi-ooxml is enough for .xls too, because it depends on poi. If the project reads .xls only, depend on poi alone and skip the much larger OOXML jars.

Gradle (Kotlin DSL)

The same dependencies in build.gradle.kts, with the version kept in one place.

val poiVersion = "5.5.1"

dependencies {
    // .xlsx (XSSF, SXSSF) and .docx (XWPF)
    implementation("org.apache.poi:poi-ooxml:$poiVersion")

    // Optional: legacy .doc, .ppt and .msg
    implementation("org.apache.poi:poi-scratchpad:$poiVersion")
}

Never mix POI versions

All org.apache.poi artifacts of a project must have exactly the same version. The jars share internal classes and package layouts, and a poi jar of one release with a poi-ooxml of another typically fails at run time with NoSuchMethodError, NoClassDefFoundError or a changed behaviour that is hard to trace. The usual way in is a transitive dependency: a third-party library (a report engine, a Tika parser module, an old internal starter) that declares its own POI version, and the build tool picks a different one for part of the artifacts.

Check the resolved versions, and force the whole group when a library drags in another release.

mvn dependency:tree -Dincludes=org.apache.poi
./gradlew dependencies --configuration runtimeClasspath | grep 'org.apache.poi'
configurations.all {
    resolutionStrategy.eachDependency {
        if (requested.group == "org.apache.poi") {
            useVersion(poiVersion)
        }
    }
}

In Maven, declare the POI artifacts you use directly (as above) so that "nearest definition wins" selects your version, or list them in dependencyManagement with ${poi.version}.

Source: Apache POI, FAQ (versions must match) and the Gradle dependency resolution documentation.

Transitive dependencies

poi-ooxml 5.5.1 brings in a small set of libraries. Their versions below come from the published POM files and change from release to release, so read them with dependency:tree rather than relying on this table.

Library Version in POI 5.5.1 Role

org.apache.xmlbeans:xmlbeans

5.3.0

Binds the OOXML schemas to Java classes (the CT* beans). Every XSSF and XWPF object wraps one.

org.apache.logging.log4j:log4j-api

2.24.3 (BOM)

Only the logging API; there is no logging implementation (see the next section).

org.apache.commons:commons-compress

1.28.0

Zip handling and streaming.

commons-io:commons-io

2.21.0

Stream helpers.

org.apache.commons:commons-collections4

4.5.0

Collections used internally.

commons-codec:commons-codec, org.apache.commons:commons-math3, com.zaxxer:SparseBitSet

1.20.0, 3.6.1, 1.3

Through poi: encoding, math functions for formula evaluation, and bit sets.

com.github.virtuald:curvesapi

1.08

Geometry support for Visio and drawing shapes.

Some other dependencies (Bouncy Castle, Apache Santuario xmlsec, Batik, PDFBox) are declared as optional in the POM: they are used for signing, encryption variants and drawing features and are not on your classpath unless you add them. The need for Bouncy Castle with signatures is covered on the encryption page of this section.

XMLBeans 5.4.1 is available on Maven Central, but the POI project documents that XMLBeans 5.2.2 together with log4j-api 2.24.1 has an issue, so do not override the XMLBeans version without testing. Source: https://poi.apache.org/ (home page) and the Apache XMLBeans site.

Lite versus full OOXML schemas

By default poi-ooxml depends on poi-ooxml-lite, which contains only the schema classes that the POI tests touch. That keeps a service small (about 6 MB instead of about 14 MB). The trade-off is that a low-level call can reach a class that is not in the lite jar. The symptom is a stack trace such as:

java.lang.NoClassDefFoundError: org/openxmlformats/schemas/drawingml/x2006/chart/CTSomething

The fix recommended by the POI FAQ is to use poi-ooxml-full instead of lite. In Maven, exclude lite from poi-ooxml and add full:

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>${poi.version}</version>
    <exclusions>
        <exclusion>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-lite</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml-full</artifactId>
    <version>${poi.version}</version>
</dependency>

In Gradle, exclude lite on the dependency and add full:

dependencies {
    implementation("org.apache.poi:poi-ooxml:$poiVersion") {
        exclude(group = "org.apache.poi", module = "poi-ooxml-lite")
    }
    implementation("org.apache.poi:poi-ooxml-full:$poiVersion")
}

Source: Apache POI, components overview, which lists poi-ooxml-full as the alternative to lite.

Do not put lite and full on the classpath together: they contain the same classes, and both declare the same module name (see JPMS below). Prefer the lite jar and switch only when the error appears, because the full jar adds startup and packaging cost. Chart creation, digital signatures and some drawing features are the typical reasons for the switch; the exact pages where that matters are marked in the later pages of this section.

The logging bridge

Since POI 5.1.0 the only logging dependency is log4j-api. It is an API, not an implementation: when no Log4j 2 core is present, Log4j falls back to a minimal console logger and prints a status message saying that no logging implementation was found. In a Spring Boot service that means POI warnings (for example a missing font or a skipped feature) either go to the console in a different format or are lost, and they never reach the Logback configuration that everything else uses.

Spring Boot already solves this. spring-boot-starter-logging (a dependency of every starter) includes log4j-to-slf4j, which implements the Log4j API on top of SLF4J, so POI messages are written by Logback. With Spring Boot 4.1.1 nothing has to be added to the POM.

In an application that is not Spring Boot and uses Logback or another SLF4J binding, add the bridge yourself.

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-to-slf4j</artifactId>
    <version>${log4j.version}</version>
</dependency>
dependencies {
    implementation("org.apache.logging.log4j:log4j-to-slf4j:$log4jVersion")
}

Use the same Log4j 2 version as the log4j-api that is resolved (Spring Boot 4.1.1 manages 2.25.5). Never add log4j-slf4j-impl or log4j-slf4j2-impl at the same time as log4j-to-slf4j: they route in opposite directions and make an endless loop. To control the verbosity of POI, configure the logger for the org.apache.poi package in the Logback configuration, see Logging in Spring Boot.

Spring Boot dependency management

Spring Boot’s spring-boot-dependencies BOM (version 4.1.1) manages log4j-api through its log4j2.version property, but it does not manage any org.apache.poi artifact and does not manage Apache XMLBeans. That has two consequences.

  • You must write the POI version yourself, as in the examples above, for both the Maven parent and the Gradle io.spring.dependency-management plug-in.

  • Upgrading Spring Boot may silently change the log4j-api that POI sees. It is a compatible API, so this is harmless, but it is the reason dependency:tree may show a log4j-api newer than the one in the POI POM.

If a Spring Boot property is available to pin versions, it is the property you define yourself (poi.version above). The Spring Boot starter basics are in Spring Boot getting started.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.1.1</version>
</parent>

JPMS module names

POI jars are not Java modules with a module-info.class; they are automatic modules, and their names are fixed by an Automatic-Module-Name entry in the manifest. The names in 5.5.1 (read from the jar manifests) are below. All five jars are multi-release jars.

Artifact Automatic module name

poi

org.apache.poi.poi

poi-ooxml

org.apache.poi.ooxml

poi-ooxml-lite

org.apache.poi.ooxml.schemas

poi-ooxml-full

org.apache.poi.ooxml.schemas

poi-scratchpad

org.apache.poi.scratchpad

A modular application declares them in its own module-info.java:

module com.bookshelf.reports {
    requires org.apache.poi.ooxml;
    requires org.apache.poi.poi;
}

Two points are worth remembering. First, lite and full have the same module name, so the module system will refuse to resolve both on the module path; this is one more reason to use only one. Second, automatic modules read every other module and the class path, so running a Spring Boot fat jar on the class path (the default) avoids all of this. The module system itself is covered in Packages and Modules.

Source: Java SE 21, ModuleFinder (automatic modules) and the META-INF/MANIFEST.MF of each jar.

OSGi notes

The POI 5.5.1 manifests contain Automatic-Module-Name but no Bundle-SymbolicName, so the plain jars are not OSGi bundles. In an OSGi container (Karaf, Equinox, Felix) there are two usual routes: wrap the jars into bundles with a tool such as bnd, or use a distribution that provides ready-made bundles. Keep these points in mind in both cases.

  • POI and XMLBeans use reflection and the context class loader to find schema classes and factories, so every package must be visible to the bundle that loads them.

  • The multi-release jars should be wrapped with the tool’s multi-release support, or the JDK 9+ classes are lost.

  • The same single-version rule applies: one poi, one poi-ooxml and one schema jar per container.

This is advice about packaging, not a tested recipe; verify with your own container before relying on it.

Dependency table for the Bookshelf scenario

This table maps each Bookshelf file to the dependencies it needs, so the right set can be chosen up front.

Bookshelf task Dependencies Notes

books-inventory.xlsx export

poi-ooxml

Chart creation may require poi-ooxml-full.

loans-report.xlsx streaming export

poi-ooxml

SXSSF is in poi-ooxml; no extra jar.

loan-letter.docx and monthly-report.docx

poi-ooxml

XWPF; some CT* beans need full.

books-import.xlsx upload

poi-ooxml

Reading with XSSFReader is in the same jar.

book-request-form.docx extraction

poi-ooxml

Content controls may use low-level beans.

Upload of old .xls files

poi (through poi-ooxml)

HSSF.

Upload of old .doc files

poi-scratchpad

HWPF.

Password-protected files, signatures

poi-ooxml, sometimes poi-ooxml-full and Bouncy Castle

See the encryption page of this section.

Log output

log4j-to-slf4j

Already present with Spring Boot.

Checklist

  • Use poi-ooxml for .xlsx and .docx, poi for .xls only and poi-scratchpad for .doc.

  • Keep all POI artifacts at one version through a property and check with dependency:tree.

  • Start with lite; switch to full on NoClassDefFoundError: org/openxmlformats/schemas/..., never both.

  • Make sure Log4j API output reaches your logging: Spring Boot does it, others add log4j-to-slf4j.

  • Spring Boot does not manage the POI version; you do.