Lightweight, annotation-driven Excel import/export mapper for Java (JDK 17+).
spring-excel-mapper provides annotation-driven mapping between Excel Workbook/Sheet and Java POJOs, plus utilities for exporting objects into Excel templates while preserving styles.
Key capabilities:
@ExcelSheet, @ExcelRow, @ExcelSection, @ExcelColumn for model mappingExcelMapper with parseWorkbook and parseWorkbookWithResult (returns ImportResult<T>)ExcelExporter helpers for filling template rows/sections and copying sheetsExcelValidator and ExcelTypeConverter (enum/date/primitive conversions)ExcelUtils for column letter/index, style extraction, sheet copy and small helpersQuick example:
@ExcelSheet
@ExcelRow(startRowIndex = 1)
public class StudentExcelModel {
@ExcelColumn(col = "A", required = true)
public String name;
}
Workbook wb = WorkbookFactory.create(inputStream);
ImportResult<StudentExcelModel> result =
ExcelMapper.parseWorkbookWithResult(wb, StudentExcelModel.class, false);
For usage patterns and examples, see spring-tools-simples README.
org.springtools.excel.annotation)Define mapping metadata on your model classes:
@ExcelSheet
sheetName to specify which sheet to parse (default: first sheet)@ExcelRow
startRowIndex: starting row (1-based, usually header is row 0, data starts at row 1)skipRowByBlank: skip empty rows if true@ExcelSection
sectionKeyword: keyword to search for (e.g., “Student List”)keywordInCol: column to search in (e.g., “A”)startRowIndex: rows to read after finding keywordstopKeyword: optional keyword to stop reading@ExcelColumn
col: column letter (e.g., “A”, “B”, “AA”)required: validation flagunique: check uniqueness across rowsuniqueByBlankCols: columns to ignore when blank for uniqueness checkenumClass: for enum conversionmin, max: numeric range validationregex: regular expression validation (e.g., email patterns)dateFormat: custom date parsing formatdefaultVal: default value when cell is emptyExcelMapper — reads Excel → Java objects
Main methods:
parseWorkbook(Workbook, Class<T>) → List<T> (throws on error)parseWorkbookWithResult(Workbook, Class<T>, boolean failFast) → ImportResult<T> (collects errors)parseWorkbookBySheet(Workbook, Class<T>, boolean includeEmpty) → Map<String, List<T>>parseSheetSection(Sheet, Class<T>) — for @ExcelSection modelsExcelExporter — writes Java objects → Excel templates
Main methods:
fillTableRows(Sheet, int startRow, List<T> data, Class<T>) — fills data into template rowsfillSection(Sheet, sectionKeyword, keywordInCol, List<T>, Class<T>) — fills a sectioncopySheetWithStyles(Sheet source, Workbook target, String newName) — clones sheet with formattingExcelValidator
required, unique, min, max, regex)ValidationResult with row/column error detailsExcelMapperregex patternsExcelTypeConverter
ExcelUtils — low-level helpers
colLetterToIndex("A") → 0, colLetterToIndex("AA") → 26colIndexToLetter(0) → “A”prepareBindings(Class<?>) → List<ColumnBinding> (reflection-based, cached)getCellValue(Cell), setCellValue(Cell, Object)copyStyle(Cell source, Cell target)copyRow(Row source, Row target)Column mapping uses Excel column letters (A, B, C, … Z, AA, AB, …). ExcelUtils.colLetterToIndex converts letters to 0-based indices for POI API usage.
Annotations use 1-based “natural” numbering (row 1 = first row in Excel UI). Code typically does -1 when calling POI APIs.
ExcelUtils.prepareBindings(Class) reads clazz.getDeclaredFields() and caches bindings. Field declaration order currently matters. Consider adding order() to @ExcelColumn for explicit ordering in future versions.
@ExcelSection uses sectionKeyword + keywordInCol to locate a data block. ExcelMapper.parseSheetSection scans for the keyword, then reads rows below it until stopKeyword or sheet end.
parseWorkbook(...) throws exceptions on first error (legacy mode)parseWorkbookWithResult(..., failFast=false) collects all errors in ImportResult<T> with errors: List<ValidationResult>ValidationResult contains row number, column, error message for user-friendly reporting// Parse all sheets, throw on error
List<T> parseWorkbook(Workbook workbook, Class<T> clazz)
// Parse with error collection
ImportResult<T> parseWorkbookWithResult(Workbook workbook, Class<T> clazz, boolean failFast)
// Parse by sheet name
Map<String, List<T>> parseWorkbookBySheet(Workbook workbook, Class<T> clazz, boolean includeEmpty)
// Parse section-based models
List<T> parseSheetSection(Sheet sheet, Class<T> clazz)
// Fill template rows
void fillTableRows(Sheet sheet, int startRow, List<T> dataList, Class<T> clazz)
// Fill section in template
void fillSection(Sheet sheet, String sectionKeyword, String keywordInCol,
List<T> dataList, Class<T> clazz)
// Clone sheet with styles
Sheet copySheetWithStyles(Sheet sourceSheet, Workbook targetWorkbook, String newSheetName)
// Validate single object
ValidationResult validate(Object obj, int rowNum, Class<?> clazz)
// Validate list (checks unique constraints)
List<ValidationResult> validateList(List<T> list, Class<T> clazz)
// Convert cell value to target type
<T> T convertValue(Object value, Class<T> targetType, String dateFormat)
getDeclaredFields() order. To ensure stability across compilers and refactoring:
order() property to @ExcelColumnRuntimeException. Consider adding:
ExcelParseException with sheet/row/col contextValidationException with collected errorsprepareBindings now uses cache, but consider:
skipRowByBlank, unique constraintsSee spring-tools-simples README for: