excel-import

excel-import v0.1.0

Stream `.xlsx` into PostgreSQL — memory stays flat, errors come back color-coded.

Get started View on GitHub Maven Central

Features

  • Streaming SAX reader

    Heap usage is independent of row count — import files that don’t fit in memory.

  • Annotation mapping

    Map rows to POJOs with @ExcelSheet / @ExcelColumn / @TargetTable.

  • Jakarta Bean Validation

    Per-row validation with @NotNull, @Size, @PastOrPresent and more.

  • Multi-row INSERT batching

    One INSERT per batch, one transaction per batch, recursive bisection on failure.

  • Color-coded Excel report

    Inserted rows green, rejected rows red, reason in an added column.

  • ON CONFLICT + Spring Boot starter

    Conflict strategies (doNothing/doUpdate) and an optional Spring Boot autoconfigure.

How it works

The file is read twice. Pass 1 streams data through mapping, validation and batched insert; between passes only a compact outcome map lives in memory (~1 MB per million rows; messages spill to a temp file). Pass 2 re-reads the file and writes a color-coded report via SXSSF.

Pass 1: data outcome map Pass 2: report

Install

// Gradle (Kotlin DSL)
implementation("org.novgorodtsev.excelimport:excel-import-core:0.1.0")
// Spring Boot starter (core pulled transitively)
implementation("org.novgorodtsev.excelimport:excel-import-spring-boot-starter:0.1.0")
<!-- Maven -->
<dependency>
<groupId>org.novgorodtsev.excelimport</groupId>
<artifactId>excel-import-core</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>org.novgorodtsev.excelimport</groupId>
<artifactId>excel-import-spring-boot-starter</artifactId>
<version>0.1.0</version>
</dependency>

Quickstart

@ExcelSheet(name = "Employees", headerRow = 0)
@TargetTable(schema = "hr", name = "employee")
public class EmployeeRow {
@ExcelColumn(header = "Personnel No") @Column("personnel_no") @NotNull
private Long personnelNo;
@ExcelColumn(header = "Full Name") @Column("full_name") @NotBlank @Size(max = 200)
private String fullName;
@ExcelColumn(header = "Hired At", formats = {"dd.MM.yyyy", "yyyy-MM-dd"})
@Column("hired_at") @PastOrPresent
private LocalDate hiredAt;
@ExcelColumn(header = "Salary") @Column("salary") @DecimalMin("0.00")
private BigDecimal salary;
@ExcelColumn(header = "Department", required = false) @Column("department")
private String department;
// getters/setters
}
ImportConfig config = ImportConfig.builder()
.batchSize(1_000)
.conflictStrategy(ConflictStrategy.doNothing("personnel_no"))
.reportPath(Path.of("/var/reports/employees-report.xlsx"))
.maxErrors(10_000)
.build();
ExcelImporter<EmployeeRow> importer = ExcelImporter.builder(EmployeeRow.class)
.dataSource(dataSource)
.config(config)
.build();
ImportReport report = importer.importFile(Path.of("employees.xlsx"));