🇰🇷 한국어 버전

Still crafting Excel reports cell by cell with POI?

If you've ever built Excel reports in a JVM environment, you know the pain of Apache POI.

// Using Apache POI directly...
val workbook = XSSFWorkbook()
val sheet = workbook.createSheet("Employee Status")
val headerRow = sheet.createRow(0)
headerRow.createCell(0).setCellValue("Name")
headerRow.createCell(1).setCellValue("Position")
headerRow.createCell(2).setCellValue("Salary")

employees.forEachIndexed { index, emp ->
    val row = sheet.createRow(index + 1)
    row.createCell(0).setCellValue(emp.name)
    row.createCell(1).setCellValue(emp.position)
    row.createCell(2).setCellValue(emp.salary.toDouble())
}

// Column widths, styles, formulas, charts... it never ends

One header, one cell, one style at a time... hundreds of lines for a single report.
Add charts, conditional formatting, formulas, and images on top? The code spirals out of control.

TBEG (Template Based Excel Generator) solves this problem at its root.


What is TBEG?

A JVM library that generates reports by binding data to Excel templates.

The core idea is simple:
1. A designer (or you) designs the report layout directly in Excel
2. Place ${variableName} markers where data should go
3. In your code, just pass the data
4. TBEG combines template + data to produce the final report

// Using TBEG - this is all you need to do
val data = mapOf(
    "title" to "Employee Status",
    "employees" to employeeList
)

ExcelGenerator().use { generator ->
    val bytes = generator.generate(template, data)
    File("output.xlsx").writeBytes(bytes)
}

Formatting, charts, formulas, and conditional formatting are all managed in the template. Your code focuses solely on data binding.


Design Philosophy

We don't reinvent what Excel already does well.

  • Aggregation with Excel's =SUM() formula, averages with =AVERAGE()
  • Conditional highlighting with conditional formatting
  • Visualization with charts

Use the familiar Excel features as they are.
TBEG adds dynamic data binding on top, and automatically adjusts these features to work as intended even when data expands.


At a Glance

Template

Template

Code

val data = simpleDataProvider {
    value("reportTitle", "Q1 2026 Sales Performance Report")
    value("period", "Jan 2026 ~ Mar 2026")
    value("author", "Yongho Hwang")
    value("reportDate", LocalDate.now().toString())
    image("logo", logoBytes)
    imageUrl("ci", "https://example.com/ci.png")  // URL supported
    items("depts") { deptList.iterator() }
    items("products") { productList.iterator() }
    items("employees") { employeeList.iterator() }
}

ExcelGenerator().use { generator ->
    generator.generateToFile(template, data, outputDir, "quarterly_report")
}

Result

Result

Variable substitution, image insertion, repeat data expansion, automatic cell merge, formula range adjustment, conditional formatting duplication, and chart data reflection - TBEG handles all of this automatically.


Key Features

FeatureDescription
Template-based generationGenerate reports by binding data to Excel templates
Repeat data processingExpand list data into rows/columns with ${repeat(...)} syntax
Variable substitutionBind values to cells, charts, shapes, headers/footers, formula arguments.
Image insertionInsert dynamic images via byte arrays or URLs
Automatic cell mergeAuto-merge consecutive cells with the same value in repeat data
BundleGroup multiple elements into a single unit that moves together
Selective field visibilityRestrict visibility of specific fields based on conditions. Choose between DELETE or DIM modes
Formula auto-adjustmentAutomatically update formula ranges (SUM, AVERAGE, etc.) on data expansion
Automatic conditional formattingAutomatically apply original conditional formatting to repeated rows
Chart/pivot table auto-reflectionAutomatically reflect expanded data ranges in charts. Pivot table source range auto-adjustment
File encryptionSet open password for generated Excel files
Document metadataSet document properties such as title, author, keywords, etc.
Large-scale processingReliably process over 1 million rows with low CPU utilization
Asynchronous processingProcess large data in the background
Lazy loadingMemory-efficient data processing via DataProvider
Spring Boot supportEasy integration with auto-configuration

Template Syntax Preview

SyntaxDescriptionExample
${variableName}Variable substitution${title}
${object.field}Repeat item field${emp.name}
${repeat(collection, range, object)}Repeat processing${repeat(items, A2:C2, item)}
${image(name)}Image insertion${image(logo)}
${merge(object.field)}Automatic cell merge${merge(emp.dept)}
${bundle(range)}Bundle${bundle(A5:H12)}
${hideable(object.field, range, mode)}Selective field visibility${hideable(emp.salary, C1:C3, DIM)}

Performance

TBEG internally generates data using streaming, producing 1 million rows in about 9 seconds while using less than 9% of system CPU. Both rendering and post-processing operate in streaming mode, using a constant memory buffer regardless of data size.

Data SizeTimeCPU Usage
1,000 rows20ms23.5%
10,000 rows109ms14.7%
30,000 rows315ms12.5%
100,000 rows993ms10.8%
1,000,000 rows8,952ms8.8%

Comparison with Other Libraries (30,000 rows)

LibraryTime
TBEG0.3s
JXLS5.2s

Most Common Use Case: Excel Download API

In practice, TBEG is most frequently used for Excel download features in web applications.

@GetMapping("/api/reports/download")
fun downloadReport(response: HttpServletResponse) {
    val data = reportService.getReportData()
    val template = resourceLoader.getResource("classpath:templates/report.xlsx")
    
    response.contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    response.setHeader("Content-Disposition", "attachment; filename=report.xlsx")
    
    excelGenerator.generate(template.inputStream, data)
        .let { response.outputStream.write(it) }
}

Admin page list exports, settlement report downloads, monthly report generation -
set up one template and this is all the code you need.


When to Use TBEG

✅ Generating standardized reports/statements
✅ Filling data into Excel forms provided by a designer
✅ Reports requiring complex formatting (conditional formatting, charts)
✅ Processing large datasets with tens to hundreds of thousands of rows
✅ Building tables with dynamically changing row/column structures
✅ Implementing Excel download APIs in Spring Boot environments
❌ Reading/parsing Excel files (TBEG is generation-only)


Getting Started

Add Dependency

// build.gradle.kts
dependencies {
    implementation("io.github.jogakdal:tbeg:1.2.3")
}
// Gradle (Groovy DSL)
dependencies {
    implementation 'io.github.jogakdal:tbeg:1.2.3'
}
<!-- Maven -->
<dependency>
    <groupId>io.github.jogakdal</groupId>
    <artifactId>tbeg</artifactId>
    <version>1.2.3</version>
</dependency>

The version above is as of this writing. Check the latest version on Maven Central.

Quick Start (Kotlin)

import io.github.jogakdal.tbeg.ExcelGenerator
import java.io.File

data class Employee(val name: String, val position: String, val salary: Int)

fun main() {
    val data = mapOf(
        "title" to "Employee Status",
        "employees" to listOf(
            Employee("Yongho Hwang", "Director", 8000),
            Employee("Yongho Han", "Manager", 6500)
        )
    )

    ExcelGenerator().use { generator ->
        val template = File("template.xlsx").inputStream()
        val bytes = generator.generate(template, data)
        File("output.xlsx").writeBytes(bytes)
    }
}

Quick Start (Java)

import io.github.jogakdal.tbeg.ExcelGenerator;
import java.io.*;
import java.nio.file.*;
import java.util.*;

public class Example {
    public static void main(String[] args) throws Exception {
        var data = Map.<String, Object>of(
            "title", "Employee Status",
            "employees", List.of(
                Map.of("name", "Yongho Hwang", "position", "Director", "salary", 8000),
                Map.of("name", "Yongho Han", "position", "Manager", "salary", 6500)
            )
        );

        try (var generator = new ExcelGenerator()) {
            byte[] bytes = generator.generate(
                new FileInputStream("template.xlsx"), data);
            Files.write(Path.of("output.xlsx"), bytes);
        }
    }
}

Learn More

This is the first post in the TBEG series. In the next post, we'll walk through creating a template and binding data step by step.

GitHub: jogakdal/data-processors-with-excel
Maven Central: tbeg

📖 Documentation

0개의 댓글