{"id":2301,"date":"2026-07-20T12:41:36","date_gmt":"2026-07-20T10:41:36","guid":{"rendered":"https:\/\/kindsonthegenius.com\/blog\/spring-batch-tutorial-reading-and-writing-excel\/"},"modified":"2026-08-27T17:41:17","modified_gmt":"2026-08-27T15:41:17","slug":"spring-batch-tutorial","status":"publish","type":"post","link":"https:\/\/kindsonthegenius.com\/blog\/spring-batch-tutorial\/","title":{"rendered":"Spring Batch Tutorial: Reading and Writing Excel"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><em>Learn how to build a Spring Batch job that reads Excel files with Apache POI, processes data in chunks, and writes results to a database.<\/em><\/p>\n\n\n<p><!-- ktg-updated-banner --><\/p>\n<p><em>Updated August 2026 \u2014 full tutorial restored for this URL.<\/em><\/p>\n<h2>TL;DR<\/h2>\n<ul>\n<li>\n<p><strong>Spring Batch and Apache POI<\/strong> can be combined to read structured Excel data in batch jobs.<\/p>\n<\/li>\n<li>\n<p>Build a custom <strong>ItemReader<\/strong> to read product rows from an Excel worksheet.<\/p>\n<\/li>\n<li>\n<p>Use an <strong>ItemProcessor<\/strong> to validate, filter, and transform records before writing them.<\/p>\n<\/li>\n<li>\n<p><strong>Chunk processing<\/strong> lets Spring Batch process and write records in manageable groups.<\/p>\n<\/li>\n<li>\n<p>For large Excel files, use <strong>streaming approaches<\/strong> instead of loading the entire workbook into memory.<\/p>\n<\/li>\n<\/ul>\n<p>In this tutorial we build a <strong>Spring Batch<\/strong> job that <strong>reads Excel<\/strong> (Apache POI) and writes processed rows in chunks \u2014 the same pattern you use for imports in production.<\/p>\n<ol>\n<li><a href=\"#t1\">Dependencies<\/a><\/li>\n<li><a href=\"#t2\">Domain model and Excel layout<\/a><\/li>\n<li><a href=\"#t3\">ItemReader with POI<\/a><\/li>\n<li><a href=\"#t4\">Processor and ItemWriter<\/a><\/li>\n<li><a href=\"#t5\">Job and Step configuration<\/a><\/li>\n<li><a href=\"#t6\">Run and verify<\/a><\/li>\n<\/ol>\n<p><strong id=\"t1\">1. Dependencies<\/strong><\/p>\n<pre><code>&lt;dependency&gt;\n  &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n  &lt;artifactId&gt;spring-boot-starter-batch&lt;\/artifactId&gt;\n&lt;\/dependency&gt;\n&lt;dependency&gt;\n  &lt;groupId&gt;org.apache.poi&lt;\/groupId&gt;\n  &lt;artifactId&gt;poi-ooxml&lt;\/artifactId&gt;\n  &lt;version&gt;5.2.5&lt;\/version&gt;\n&lt;\/dependency&gt;\n<\/code><\/pre>\n<p>Enable batch schema: <code>spring.batch.jdbc.initialize-schema=always<\/code> for local demos.<\/p>\n<p><strong id=\"t2\">2. Domain model and Excel layout<\/strong><\/p>\n<p>Assume sheet <code>products<\/code> with columns: <code>sku<\/code>, <code>name<\/code>, <code>price<\/code>.<\/p>\n<pre><code>public record ProductRow(String sku, String name, BigDecimal price) {}\n<\/code><\/pre>\n<p><strong id=\"t3\">3. ItemReader with POI<\/strong><\/p>\n<pre><code>public class ExcelProductReader implements ItemReader&lt;ProductRow&gt; {\n  private Iterator&lt;Row&gt; rows;\n\n  public ExcelProductReader(Resource resource) throws Exception {\n    Workbook wb = WorkbookFactory.create(resource.getInputStream());\n    Sheet sheet = wb.getSheet(\"products\");\n    rows = sheet.iterator();\n    if (rows.hasNext()) rows.next(); \/\/ skip header\n  }\n\n  @Override\n  public ProductRow read() {\n    if (rows == null || !rows.hasNext()) return null;\n    Row r = rows.next();\n    return new ProductRow(\n        r.getCell(0).getStringCellValue(),\n        r.getCell(1).getStringCellValue(),\n        BigDecimal.valueOf(r.getCell(2).getNumericCellValue()));\n  }\n}\n<\/code><\/pre>\n<p>For large files, prefer streaming (<code>SXSSF<\/code> \/ event API) instead of loading the whole workbook.<\/p>\n<p><strong id=\"t4\">4. Processor and ItemWriter<\/strong><\/p>\n<pre><code>@Bean\nItemProcessor&lt;ProductRow, ProductRow&gt; processor() {\n  return item -&gt; {\n    if (item.price().signum() &lt; 0) return null; \/\/ filter\n    return new ProductRow(item.sku().trim().toUpperCase(), item.name().trim(), item.price());\n  };\n}\n\n@Bean\nJdbcBatchItemWriter&lt;ProductRow&gt; writer(DataSource ds) {\n  return new JdbcBatchItemWriterBuilder&lt;ProductRow&gt;()\n      .dataSource(ds)\n      .sql(\"INSERT INTO product(sku,name,price) VALUES (:sku,:name,:price)\")\n      .beanMapped()\n      .build();\n}\n<\/code><\/pre>\n<p><strong id=\"t5\">5. Job and Step configuration<\/strong><\/p>\n<pre><code>@Bean\nJob importJob(JobRepository jobs, Step step1) {\n  return new JobBuilder(\"importExcelJob\", jobs).start(step1).build();\n}\n\n@Bean\nStep step1(JobRepository jobs, PlatformTransactionManager tx,\n           ExcelProductReader reader,\n           ItemProcessor&lt;ProductRow, ProductRow&gt; processor,\n           JdbcBatchItemWriter&lt;ProductRow&gt; writer) {\n  return new StepBuilder(\"excelToDb\", jobs)\n      .&lt;ProductRow, ProductRow&gt;chunk(100, tx)\n      .reader(reader)\n      .processor(processor)\n      .writer(writer)\n      .build();\n}\n<\/code><\/pre>\n<p><strong id=\"t6\">6. Run and verify<\/strong><\/p>\n<p>Trigger with <code>JobLauncher<\/code> or <code>spring.batch.job.enabled=true<\/code>. Check <code>BATCH_JOB_EXECUTION<\/code> and your <code>product<\/code> table. Next step: write an <code>ItemWriter<\/code> that exports Excel for reports (mirror the reader with <code>XSSFWorkbook<\/code>).<\/p>\n<h2>Final Thoughts<\/h2>\n<p>Spring Batch provides a reliable structure for turning Excel imports into repeatable processing jobs. By separating reading, processing, and writing into dedicated components, you can keep the workflow easier to test, maintain, and extend.<\/p>\n<p>The example here uses Apache POI to read Excel rows, applies simple validation and transformation, and writes the results to a database in chunks. For production workloads, file size and memory usage should guide your choice of Excel processing approach, especially when dealing with large workbooks.<\/p>\n<p>Once the basic import works, the same Spring Batch pattern can be extended to more complex validation, error handling, database operations, reporting, and Excel exports.<\/p>","protected":false},"excerpt":{"rendered":"<p>Learn how to build a Spring Batch job that reads Excel files with Apache POI, processes data in chunks, and writes results to a database. &hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"pagelayer_contact_templates":[],"_pagelayer_content":"","footnotes":""},"categories":[85],"tags":[],"class_list":["post-2301","post","type-post","status-publish","format-standard","hentry","category-java"],"acf":[],"_links":{"self":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts\/2301","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/comments?post=2301"}],"version-history":[{"count":3,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts\/2301\/revisions"}],"predecessor-version":[{"id":2478,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts\/2301\/revisions\/2478"}],"wp:attachment":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/media?parent=2301"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/categories?post=2301"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/tags?post=2301"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}