Library / SDK
ozlerhakan/poiji avatar
ozlerhakan/poiji

Poiji: mapping Excel rows to Java objects with Apache POI

:candy: A library converting XLS and XLSX files to a list of Java objects based on Apache POI

532 stars138 forksJavaMIT

At a glance

What is it?
Poiji is a small thread-safe Java library that converts rows of an XLS or XLSX file into instances of a class you define, using annotations to say which cell goes in which field. It sits on Apache POI 5.5.1 and adds a builder of options for dates, delimiters, sheet selection and number formats on top.
Who is it for?
Poiji suits a Java service that receives spreadsheets from users or operators and needs typed objects rather than cell-by-cell parsing, since the annotation model keeps the mapping declarative and the options builder covers the irregularities real spreadsheets have.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 103 days ago.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What problem Poiji solves

Poiji is described as a tiny thread-safe Java library that provides one way of mapping from Excel sheets to Java classes. In a way it lets us convert each row of the specified Excel data into Java objects.

The word that carries the weight is one way. There is no fluent builder, no DSL and no runtime configuration file. You annotate your class, you call a static method, you get a list back.

The problem it addresses is the one every Java team hits when a business user sends a spreadsheet. Apache POI gives you the low-level access to a workbook, but it gives it to you cell by cell, and turning a sheet into a list of domain objects with POI directly is a hundred lines of index arithmetic that you write again for every new spreadsheet. Poiji sits between the two: it keeps POI underneath and gives you the objects at the top.

The topic list tells you the scope precisely. The library sits in the apache-poi ecosystem with topics for excel, microsoft-excel, converter, deserialize, unmarshall, mapper, mapping, pojo, java-11, java-17, java-23 and java-records, which describes a Java 11 and later library for deserialising spreadsheets into plain objects. The licence is MIT, the primary language is Java, and the last push to the repository was on 2026-07-12 with the newest release at v5.4.0 from 2026-04-11.

The annotation model

The mapping is expressed with two annotations and one named-cell annotation. Here is the object model from the documentation:

java
public class Employee {

    @ExcelRow                    <1>
    private int rowIndex;

    @ExcelCell(0)                <2>
    private long employeeId;     <3>

    @ExcelCell(1)
    private String name;

    @ExcelCell(2)
    private String surname;

    @ExcelCell(3)
    private int age;

    @ExcelCell(4)
    private boolean single;

    @ExcelCellName("emails")     <4>
    List<String> emails;

    @ExcelCell(5)
    List<BigDecimal> bills;

    //no need getters/setters to map excel cells to fields
}

The rules are laid out as callouts. The ExcelRow annotation is optional and gives you the index of each row item, and the annotated variable should be of type int, double, float or long. A field must be annotated with ExcelCell along with its property in order to get the value from the right coordinate in the target sheet. An annotated field can be protected, private or public, and may be of type boolean, int, long, float, double or their wrapper classes. You can also add a field of java.util.Date, java.time.LocalDate, java.time.LocalTime, java.time.LocalDateTime and String.

Two details matter in practice. The list handling is tied to the ExcelCellName annotation rather than ExcelCell, and the list may store items of type BigDecimal, Long, Double, Float, Integer, Boolean and String. And the comment in the class is the key ergonomic fact: you do not need getters and setters to map cells to fields, because the mapping is field-based rather than property-based.

That last point is a real difference from Jackson-style mappers, and it means a private field with no accessor is still populated, which is what you want when the class is a data holder.

Reading a workbook into a list

The call is a single static method, Poiji.fromExcel, with overloads that vary the input and the options. The documentation lists the full surface: File or InputStream or an already-open Sheet, with or without a PoijiOptions, and with or without a Consumer that lets you post-process each object as it is built.

A worked example from the docs:

java
PoijiOptions options = PoijiOptions.PoijiOptionsBuilder.settings()
        .addListDelimiter(";") <1>
        .build();
List<Employee> employees = Poiji.fromExcel(new File("employees.xls"), Employee.class, options);
// alternatively
InputStream stream = new FileInputStream(new File("employees.xls"))
List<Employee> employees = Poiji.fromExcel(stream, PoijiExcelType.XLS, Employee.class, options);

Note the difference between the two forms. When you pass a File, the format is inferred from the file itself. When you pass an InputStream there is no filename to infer from, so you have to state the type explicitly with PoijiExcelType, which is why the second call needs it.

The Consumer overload is the part that turns this from a converter into a streaming mapper. Because each object is available as it is created, a large sheet can be written to a database row by row instead of being held in memory as a list first. That is the escape hatch for the file size problem, and it is worth knowing about even if you do not need it on day one.

The documentation also gives the expected data shape. The employees.xlsx file has columns ID, NAME, SURNAME, AGE, SINGLE, BILLS and EMAILS, where BILLS and EMAILS hold multiple values separated by semicolons, which is what the addListDelimiter option addresses. The example notes that the returned list has three employees and shows the first one as Employee{rowIndex=1, employeeId=123923, name='Joe.

The options builder, and the irregularities of real spreadsheets

PoijiOptions.PoijiOptionsBuilder is where the library earns its place, because the problems with real spreadsheets are not the mapping, they are the irregularities. The documented builder methods cover a long list of them.

For dates there is dateLenient(boolean), a dateRegex(String), timeRegex(String), dateTimeRegex(String), and then datePattern(String) plus dateFormatter, timeFormatter and dateTimeFormatter taking java.time.format.DateTimeFormatter instances. So you can either give Poiji a pattern or a regular expression, or hand it a formatter for the java.time types.

For selecting what to read, there is sheetIndex(int) and sheetName(String), skip(int) and limit(int) for rows, headerStart(int) and headerCount(int) for where the header row sits and how many rows are headers, and ignoreHiddenSheets(boolean) so hidden tabs do not contribute rows.

For values, trimCellValue(boolean), caseInsensitive(boolean) and ignoreWhitespaces(boolean) handle the string tidying that dominates real data. addListDelimiter(String) sets the separator for multi-value cells, and preferNullOverDefault(boolean) decides what happens when a cell is empty and the field has a default.

For number handling there is poijiNumberFormat(PoijiNumberFormat), poijiLogCellFormat(PoijiLogCellFormat), and disableXLSXNumberCellFormat() to turn the XLSX number format handling off. There is also withCasting(Casting) and withFormatting(Formatting) as extension points.

Then there are the operational ones: password(String) for protected workbooks, settings(int) for the POI settings level, setLocale(java.util.Locale), rawData(boolean) which returns the cell values without conversion, and setIgnoreFileExtension(boolean). The builder starts with settings(), which is what the example uses before chaining.

The size of that list is the argument for the library. Each of those switches corresponds to a bug report someone filed about a spreadsheet that did not quite fit.

Properties files and the rest of the entry points

There is a second family of entry points for key/value style data, fromExcelProperties, with overloads taking a File or an InputStream plus the class and optionally the options.

That is a different shape of problem from row mapping. A properties file is a flat list of key and value pairs rather than a grid, so the mapping target is an object whose fields are populated from named entries rather than from column coordinates. If you are reading a configuration-shaped spreadsheet, this is the method you want, and it is worth knowing it exists because the name is not obvious from the main fromExcel surface.

The broader fromExcel overload set also includes the Sheet case, where you already hold an open org.apache.poi.ss.usermodel.Sheet, with and without options and with and without the Consumer. That is the path for a workbook you are already reading for other reasons, where you do not want Poiji to reopen the file.

The dependency situation is worth checking. Poiji 5.4.0 is built against Apache POI 5.5.1, per the badge in the README, and Poiji pulls POI in transitively. If your project already depends on a different POI version, that is a version conflict waiting to happen and it is the first thing to verify before writing code.

The repository layout also tells you about the testing approach. Alongside the src/ directory and pom.xml there is a jshell/ directory and a try-with-jshell.sh script, which suggests the library is exercised through the JShell interactive REPL rather than only through a conventional test harness. That is a fast way to check behaviour against a real workbook while developing, and it is a reasonable pattern for a library whose input is a file.

Where Poiji is the wrong choice

Three limits are structural. The first is that the API returns a List. Every fromExcel overload that produces objects returns them as a list, so the whole workbook is in memory as typed objects at once. For a sheet with a few thousand rows that is fine. For a spreadsheet with a million rows it is not, and while the Consumer overload lets you process objects as they arrive, it does not change the fact that POI's underlying workbook model loads the file, and POI itself has a streaming read path that Poiji does not expose. If your spreadsheets are routinely huge, you want POI's event-based API directly rather than Poiji.

The second is that Poiji is only a mapper. It does no validation, no business rules and no error reporting beyond what the mapping itself throws. There is no way to say that cell 42 must be a positive integer or that column D is required, other than checking afterwards in your own code. For a library that people reach for when handling user-submitted data, that is a meaningful gap, and it is one you have to close yourself.

The third is the coupling to POI's model, which shows through in the API. Options like poijiNumberFormat, disableXLSXNumberCellFormat and the fact that one of the fromExcel overloads takes an org.apache.poi.ss.usermodel.Sheet all mean that POI's types are part of Poiji's contract. That is fine when POI is already your spreadsheet library and awkward when it is not, since it makes the POI version a compatibility surface you cannot easily swap out.

The alternative approaches differ in kind rather than degree. Apache POI directly gives you the finest control and the most code, with cell-by-cell iteration and your own type conversion. Jackson's CSV and Apache Commons CSV handle delimited text but not the formatting and multi-sheet complexity of a real workbook. And if the spreadsheet is not really a spreadsheet but a data interchange format in disguise, a tool that speaks that format directly will do less work than any Excel library. Poiji is the choice when the input really is an Excel file with human formatting and you want typed objects without hand-writing the index arithmetic.

Editorial conclusion

Poiji suits a Java service that receives spreadsheets from users or operators and needs typed objects rather than cell-by-cell parsing, since the annotation model keeps the mapping declarative and the options builder covers the irregularities real spreadsheets have. It is the wrong tool if you need streaming for files larger than memory allows, since the API returns a list, and it is the wrong tool if you want anything beyond mapping, because it deliberately does no validation and no business logic. Verify first which Apache POI version your project already uses, since Poiji 5.4.0 targets POI 5.5.1 and brings it in as a transitive dependency, and note that the newest release is v5.4.0 from 2026-04-11 with the last push on 2026-07-12.

Frequently asked questions

What does Poiji do?

It is a tiny thread-safe Java library that maps Excel sheets to Java classes, converting each row of the specified Excel data into a Java object. It uses Apache POI under the hood to do the reading and mapping.

How do I add Poiji to a Maven project?

Add the dependency with groupId com.github.ozlerhakan, artifactId poiji and version 5.4.0. Latest and earlier development versions with javadoc and sources are published at central.sonatype.com/artifact/com.github.ozlerhakan/poiji.

How do I map an Excel column to a Java field?

Annotate the field with @ExcelCell and the column index, for example @ExcelCell(0) for the first column. The field can be protected, private or public and may be boolean, int, long, float, double, their wrappers, Date, LocalDate, LocalTime, LocalDateTime or String, and you do not need getters and setters.

How do I handle a cell that contains several values?

Use a List field annotated with @ExcelCellName, for example @ExcelCellName("emails"), and set the separator with the addListDelimiter option on PoijiOptions. The list may hold BigDecimal, Long, Double, Float, Integer, Boolean or String items.

Which Apache POI version does Poiji need?

Poiji 5.4.0 is built against Apache POI 5.5.1 and brings POI in as a transitive dependency, so an existing POI dependency in your project can conflict with it.

What options does PoijiOptions support?

The builder covers date handling with datePattern, dateRegex, dateLenient and DateTimeFormatter variants, row and sheet selection with sheetIndex, sheetName, skip, limit, headerStart, headerCount and ignoreHiddenSheets, value handling with trimCellValue, caseInsensitive, ignoreWhitespaces, preferNullOverDefault and addListDelimiter, plus password, settings, locale, rawData and number format options.

Official sources

  1. Issues
  2. License: MIT
  3. ozlerhakan/poiji on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/ozlerhakan-poiji.svg)](https://hysenlabs.com/projects/ozlerhakan-poiji)

Was this useful?