Table of content
- Excel file with Kotlin | Apache POI
- Apache POI famous Terms
- Write Excel in Kotlin
- Read Excel in Kotlin
- Retrieving Cell values by CellType
- Read all values from a specific Column
In every Development environment, data is a mandatory part of their development, we put all your data in your program files. But if any change in data results in the editing of the program file, which makes us recompile the code and retest the compiled code.
If data Changes every day, are we going to edit the program file every day?
What happens if the compilation fails ?
Development should happen in such that data do not have any effect on the program files. Placing data outside the program is the only way to do it, like placing the data on excel files, property files, config file, Json Files, Xml files.
Apache POI :
Apache POI helps Kotlin/Java related technologies to read and write Excel files on different platforms, Using apache poi we can do read and write operation of both xls and xlsx file formats. Apache poi is an open-source tool developed by Apache.
Apache POI will be helpful to modify the large content of data. Below is the step by step process to download Apache poi jar files.
Follow below steps to download Apache Poi:
1. Open https://poi.apache.org/download.html
2. Click Downloads section on right side
3. Click on the poi-bin-#####.zip link under binary distribution section
4. Click on the mirror link, apache would be suggesting the nearest location for your mirror link
5. Your poi files will be started to download
6. Extract the downloaded zip file
7. The extracted folder should contain below files.
Steps for adding Apache POI jars in IntelliJ IDEA:
- Click File from the toolbar
- Project Structure (CTRL + SHIFT + ALT + S on Windows/Linux)
- Select Modules at the left panel
- Dependencies tab
- ‘+’ → JARs or directories and add all jar files to IntelliJ
Apache Commons CSV Integration
Apache POI excel library revolves around four key interfaces which actually represent the items in the excel file.
- Workbook: A workbook represents the excel file
- Sheet: A workbook may contain many sheets. We can access the sheets either with a name or with index.
- Row: As the name suggests, It represents a row in the sheet.
- Cell: A cell represents a column in the sheet.
For writing excel file in kotlin the steps are simple, we would be writing an xlsx file in this example.
- Create an Object for XSSFWorkbook(), which will create excel file in JVM
- Create Sheet object from workbook object(xlWb) using createSheet() function
- Create a row from the Workbook sheet object(xlWb) using createRow(rowNumber), pass the which row number you want to create
- Create a Cell inside the row using createCell() function, pass the cell number as a parameter
- Set the value of the cell that you have created using the setCellValue(«value to set») function
- Now we have to move the Excel file created inside JVM into local file system using FileOutputStream
fun main(args: Array<String>) {
val filepath = "./test_file.xlsx"
//Instantiate Excel workbook:
val xlWb = XSSFWorkbook()
//Instantiate Excel worksheet:
val xlWs = xlWb.createSheet()
//Row index specifies the row in the worksheet (starting at 0):
val rowNumber = 0
//Cell index specifies the column within the chosen row (starting at 0):
val columnNumber = 0
//Write text value to cell located at ROW_NUMBER / COLUMN_NUMBER:
val xlRow = xlWs.createRow(rowNumber)
val xlCol = xlRow.createCell(columnNumber)
xlCol.setCellValue("Chercher Tech")
//Write file:
val outputStream = FileOutputStream(filepath)
xlWb.write(outputStream)
xlWb.close()
}
Write Properties File
- <li»>To read any file we have to bring the file from Local system to JVM, use
FileInputStream()
- object to bring the excel file into JVM <li»>Read the file as excel file using the
WorkbookFactory.create()
- , because sometimes you may have xlsx or xls, so to avoid issues we would be using the Workbookfactory.Create() <li»>Get the sheet we want to read using
getSheetAt(«index/name»)
- function, we can get the sheet either using the name or by index, the index starts from 0 <li»>Get the row using
getRow(rowNumber)
- from the sheet object and pass the row number you want to read s parameter <li»>Get the cell number from the row using the
getCell(columnNumber)
- function and print it
fun main(args: Array<String>) {
val filepath = "./test_file.xlsx"
val inputStream = FileInputStream(filepath)
//Instantiate Excel workbook using existing file:
var xlWb = WorkbookFactory.create(inputStream)
//Row index specifies the row in the worksheet (starting at 0):
val rowNumber = 0
//Cell index specifies the column within the chosen row (starting at 0):
val columnNumber = 0
//Get reference to first sheet:
val xlWs = xlWb.getSheetAt(0)
println(xlWs.getRow(rowNumber).getCell(columnNumber))
}
KProperty in kotlin
We can retrieve cell value using stringCellValue method but it only works for String values. In actual, day to day activities, we may store more types of data in excel sheets like Number, boolean, strings.
We have different properties to retrieve different types of data in apache poi
To retrieve different data, you may check each cell’s type and then retrieve its value using various type-specific methods.
You should understand that below properties will not extract a type of data from the cell when you store a particular data type in the cell then the total cell is of that type
So these properties will fetch total value present in the cell
- booleanCellValue — To fetch boolean data from the excel
- stringCellValue — To fetch String data from the excel
- dateCellValue — fetches date values from the cell
- numericCellValue — fetches numeric value
- cellFormula — fetches the data from the formula cell.
fun main(args: Array<String>) {
val filepath = "./test_file.xlsx"
val inputStream = FileInputStream(filepath)
//Instantiate Excel workbook using existing file:
var xlWb = WorkbookFactory.create(inputStream)
//Row index specifies the row in the worksheet (starting at 0):
val rowNumber = 0
//Cell index specifies the column within the chosen row (starting at 0):
val columnNumber = 0
//Get reference to first sheet:
val xlWs = xlWb.getSheetAt(0)
var cell = xlWs.getRow(rowNumber).getCell(columnNumber)
when (cell.getCellTypeEnum()) {
CellType.BOOLEAN -> println("Boolean value found : "+cell.booleanCellValue)
CellType.STRING -> println("String value found : "+cell.stringCellValue)
CellType.NUMERIC -> if (DateUtil.isCellDateFormatted(cell)) {
println("Date value found : "+cell.dateCellValue)
} else {
println("Numeric value found : "+cell.numericCellValue)
}
CellType.FORMULA -> println("Formula value found : "+cell.getCellFormula())
else -> print("")
}
}
Append content in kotlin
We can fetch all the data from a specific column in apache poi, here we should not change the column number. The column is specified by the cell in apache POI. We have our column number ready but we have to get the number of rows present in the excel sheet so that we would know how much we have to iterate.
For going through every row in the excel sheet we have to create rowIterator() for the excel sheet
We have all rows now, let’s get the data based on the cell/Column number. In ‘E’ column I have store months, let’s retrieve it. E column would have an index number as 4.
fun main(args: Array) {
val filepath = "./test_file.xlsx"
val inputStream = FileInputStream(filepath)
//Instantiate Excel workbook using existing file:
var xlWb = WorkbookFactory.create(inputStream)
//Row index specifies the row in the worksheet (starting at 0):
val rowNumber = 0
//Cell index specifies the column within the chosen row (starting at 0):
val columnNumber = 0
//Get reference to first sheet:
val xlWs = xlWb.getSheetAt(0)
val xlRows = xlWs.rowIterator()
// go row by row to get the values and print them
xlRows.forEach { row -> println(row.getCell(4))}
}
Using While Loop
I hope you know that the last row number would give us the number of rows present in the excel.
We can get number of rows using the lastRowNumber property from Cell object and we can use the while loop to get all the values
fun main(args: Array) {
val filepath = "./test_file.xlsx"
val inputStream = FileInputStream(filepath)
//Instantiate Excel workbook using existing file:
var xlWb = WorkbookFactory.create(inputStream)
//Row index specifies the row in the worksheet (starting at 0):
val rowNumber = 0
//Cell index specifies the column within the chosen row (starting at 0):
val columnNumber = 0
//Get reference to first sheet:
val xlWs = xlWb.getSheetAt(0)
val xlRows = xlWs.lastRowNum
var i=0;
while (i<= xlRows){
println(xlWs.getRow(i).getCell(4))
i++
}
}
0 results
In this tutorial, we’re gonna look at Kotlin examples that read and write Excel file using Apache POI.
I. Dependency
<dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>1.2.21</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.17</version> </dependency>
II. Write Data to Excel File
– Simple POJO Customer (id, name, address, age):
package com.javasampleapproach.kotlin.apachecsv class Customer { var id: String? = null var name: String? = null var address: String? = null var age: Int = 0 constructor() {} constructor(id: String?, name: String?, address: String?, age: Int) { this.id = id this.name = name this.address = address this.age = age } override fun toString(): String { return "Customer [id=" + id + ", name=" + name + ", address=" + address + ", age=" + age + "]" } }
– Write to Excel file:
package com.javasampleapproach.kotlin.apachecsv import java.io.FileOutputStream import java.io.IOException import java.util.Arrays import org.apache.poi.ss.usermodel.Cell import org.apache.poi.ss.usermodel.CellStyle import org.apache.poi.ss.usermodel.CreationHelper import org.apache.poi.ss.usermodel.Font import org.apache.poi.ss.usermodel.IndexedColors import org.apache.poi.ss.usermodel.Row import org.apache.poi.ss.usermodel.Sheet import org.apache.poi.ss.usermodel.Workbook import org.apache.poi.xssf.usermodel.XSSFWorkbook private val COLUMNs = arrayOf("Id", "Name", "Address", "Age") private val customers = Arrays.asList( Customer("1", "Jack Smith", "Massachusetts", 23), Customer("2", "Adam Johnson", "New York", 27), Customer("3", "Katherin Carter", "Washington DC", 26), Customer("4", "Jack London", "Nevada", 33), Customer("5", "Jason Bourne", "California", 36)) @Throws(IOException::class) fun main(args: Array?) { val workbook = XSSFWorkbook() val createHelper = workbook.getCreationHelper() val sheet = workbook.createSheet("Customers") val headerFont = workbook.createFont() headerFont.setBold(true) headerFont.setColor(IndexedColors.BLUE.getIndex()) val headerCellStyle = workbook.createCellStyle() headerCellStyle.setFont(headerFont) // Row for Header val headerRow = sheet.createRow(0) // Header for (col in COLUMNs.indices) { val cell = headerRow.createCell(col) cell.setCellValue(COLUMNs[col]) cell.setCellStyle(headerCellStyle) } // CellStyle for Age val ageCellStyle = workbook.createCellStyle() ageCellStyle.setDataFormat(createHelper.createDataFormat().getFormat("#")) var rowIdx = 1 for (customer in customers) { val row = sheet.createRow(rowIdx++) row.createCell(0).setCellValue(customer.id) row.createCell(1).setCellValue(customer.name) row.createCell(2).setCellValue(customer.address) val ageCell = row.createCell(3) ageCell.setCellValue(customer.age.toDouble()) ageCell.setCellStyle(ageCellStyle) } val fileOut = FileOutputStream("customers.xlsx") workbook.write(fileOut) fileOut.close() workbook.close() }
– Check results in customers.xlsx:
III. Read Data from Excel File
package com.javasampleapproach.kotlin.apachecsv import java.io.File import java.io.FileInputStream import java.io.IOException import org.apache.poi.ss.usermodel.Cell import org.apache.poi.ss.usermodel.CellType import org.apache.poi.ss.usermodel.Row import org.apache.poi.ss.usermodel.Sheet import org.apache.poi.ss.usermodel.Workbook import org.apache.poi.xssf.usermodel.XSSFWorkbook @Throws(IOException::class) fun main(args: Array?) { val excelFile = FileInputStream(File("customers.xlsx")) val workbook = XSSFWorkbook(excelFile) val sheet = workbook.getSheet("Customers") val rows = sheet.iterator() while (rows.hasNext()) { val currentRow = rows.next() val cellsInRow = currentRow.iterator() while (cellsInRow.hasNext()) { val currentCell = cellsInRow.next() if (currentCell.getCellTypeEnum() === CellType.STRING) { print(currentCell.getStringCellValue() + " | ") } else if (currentCell.getCellTypeEnum() === CellType.NUMERIC) { print(currentCell.getNumericCellValue().toString() + "(numeric)") } } println() } workbook.close() excelFile.close() }
– Check Result in Console:
Id | Name | Address | Age | 1 | Jack Smith | Massachusetts | 23.0(numeric) 2 | Adam Johnson | New York | 27.0(numeric) 3 | Katherin Carter | Washington DC | 26.0(numeric) 4 | Jack London | Nevada | 33.0(numeric) 5 | Jason Bourne | California | 36.0(numeric)
In this article, I would like to show you how to generate Excel reports in the .xls and .xlsx formats (also known as Open XML) in a Spring Boot REST API with Apache POI and Kotlin.
After finishing this guide, you will have a fundamental understanding of how to create custom cells formats, styles, and fonts. In the end, I will show you how to create Spring Boot REST endpoints so you can easily download generated files.
To better visualize what we’ll learn, check out the preview of the resulting file:
Step 1: Add the Necessary Imports
As the first step, let’s create a Spring Boot project (I highly recommend using the Spring Initializr page) and add the following imports:
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.apache.poi:poi:4.1.2")
implementation("org.apache.poi:poi-ooxml:4.1.2")
Let me explain the purpose of each library:
- The Spring Boot Starter Web is necessary to create the REST API in our application.
- The Apache POI is a complex Java library for working with Excel files. If we would like to work only with the .xls format, then the poi import would be enough. In our case, we would like to add the support for the .xlsx format, so the poi-ooxml component is necessary as well.
Step 2: Create the Models
As the next step, let’s create an enum class called CustomCellStyle with 4 constants:
enum class CustomCellStyle {
GREY_CENTERED_BOLD_ARIAL_WITH_BORDER,
RIGHT_ALIGNED,
RED_BOLD_ARIAL_WITH_BORDER,
RIGHT_ALIGNED_DATE_FORMAT
}
Although the purpose of this enum class might seem a bit enigmatic at the moment, it will all become clear in the next sections.
Step 3: Prepare Cells Styles
The Apache POI library comes with the CellStyle interface, which we can use to define custom styling and formatting within rows, columns, and cells.
Let’s create a StylesGenerator component, which will be responsible for preparing a map containing our custom styles:
@Component
class StylesGenerator {
fun prepareStyles(wb: Workbook): Map<CustomCellStyle, CellStyle> {
val boldArial = createBoldArialFont(wb)
val redBoldArial = createRedBoldArialFont(wb)
val rightAlignedStyle = createRightAlignedStyle(wb)
val greyCenteredBoldArialWithBorderStyle =
createGreyCenteredBoldArialWithBorderStyle(wb, boldArial)
val redBoldArialWithBorderStyle =
createRedBoldArialWithBorderStyle(wb, redBoldArial)
val rightAlignedDateFormatStyle =
createRightAlignedDateFormatStyle(wb)
return mapOf(
CustomCellStyle.RIGHT_ALIGNED to rightAlignedStyle,
CustomCellStyle.GREY_CENTERED_BOLD_ARIAL_WITH_BORDER to greyCenteredBoldArialWithBorderStyle,
CustomCellStyle.RED_BOLD_ARIAL_WITH_BORDER to redBoldArialWithBorderStyle,
CustomCellStyle.RIGHT_ALIGNED_DATE_FORMAT to rightAlignedDateFormatStyle
)
}
}
As you can see, with this approach, we create each style once and put it inside a map so that we will be able to refer to it later.
There are plenty of design techniques which we could use here, but I believe using a map and enum constants is one of the best ways to keep the code cleaner and easier to modify.
With that being said, let’s add some missing functions inside the generator class. Let’s start with custom fonts first:
private fun createBoldArialFont(wb: Workbook): Font {
val font = wb.createFont()
font.fontName = "Arial"
font.bold = true
return font
}
The createBoldArialFont function creates a new bold Arial Font instance, which we will use later.
Similarly, let’s implement a createRedBoldArialFont function and set the font color to red:
private fun createRedBoldArialFont(wb: Workbook): Font {
val font = wb.createFont()
font.fontName = "Arial"
font.bold = true
font.color = IndexedColors.RED.index
return font
}
After that, we can add other functions responsible for creating individual CellStyle instances:
private fun createRightAlignedStyle(wb: Workbook): CellStyle {
val style: CellStyle = wb.createCellStyle()
style.alignment = HorizontalAlignment.RIGHT
return style
}
private fun createBorderedStyle(wb: Workbook): CellStyle {
val thin = BorderStyle.THIN
val black = IndexedColors.BLACK.getIndex()
val style = wb.createCellStyle()
style.borderRight = thin
style.rightBorderColor = black
style.borderBottom = thin
style.bottomBorderColor = black
style.borderLeft = thin
style.leftBorderColor = black
style.borderTop = thin
style.topBorderColor = black
return style
}
private fun createGreyCenteredBoldArialWithBorderStyle(wb: Workbook, boldArial: Font): CellStyle {
val style = createBorderedStyle(wb)
style.alignment = HorizontalAlignment.CENTER
style.setFont(boldArial)
style.fillForegroundColor = IndexedColors.GREY_25_PERCENT.getIndex();
style.fillPattern = FillPatternType.SOLID_FOREGROUND;
return style
}
private fun createRedBoldArialWithBorderStyle(wb: Workbook, redBoldArial: Font): CellStyle {
val style = createBorderedStyle(wb)
style.setFont(redBoldArial)
return style
}
private fun createRightAlignedDateFormatStyle(wb: Workbook): CellStyle {
val style = wb.createCellStyle()
style.alignment = HorizontalAlignment.RIGHT
style.dataFormat = 14
return style
}
Please keep in mind that the above examples represent only a small part of CellStyle’s possibilities. If you would like to see the full list, please refer to the official documentation here.
Step 4: Create the ReportService Class
As the next step, let’s implement a ReportService class responsible for creating .xlsx and .xls files and returning them as ByteArray instances:
@Service
class ReportService(
private val stylesGenerator: StylesGenerator
) {
fun generateXlsxReport(): ByteArray {
val wb = XSSFWorkbook()
return generateReport(wb)
}
fun generateXlsReport(): ByteArray {
val wb = HSSFWorkbook()
return generateReport(wb)
}
}
As you can see, the only difference between these two formats’ generation is the type of Workbook implementation we’ve. used. For the .xlsx format we will use the XSSFWorkbook class, and for the .xls we will use HSSFWorkbook.
Let’s add the rest of the code to the ReportService:
private fun generateReport(wb: Workbook): ByteArray {
val styles = stylesGenerator.prepareStyles(wb)
val sheet: Sheet = wb.createSheet("Example sheet name")
setColumnsWidth(sheet)
createHeaderRow(sheet, styles)
createStringsRow(sheet, styles)
createDoublesRow(sheet, styles)
createDatesRow(sheet, styles)
val out = ByteArrayOutputStream()
wb.write(out)
out.close()
wb.close()
return out.toByteArray()
}
private fun setColumnsWidth(sheet: Sheet) {
sheet.setColumnWidth(0, 256 * 20)
for (columnIndex in 1 until 5) {
sheet.setColumnWidth(columnIndex, 256 * 15)
}
}
private fun createHeaderRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
val row = sheet.createRow(0)
for (columnNumber in 1 until 5) {
val cell = row.createCell(columnNumber)
cell.setCellValue("Column $columnNumber")
cell.cellStyle = styles[CustomCellStyle.GREY_CENTERED_BOLD_ARIAL_WITH_BORDER]
}
}
private fun createRowLabelCell(row: Row, styles: Map<CustomCellStyle, CellStyle>, label: String) {
val rowLabel = row.createCell(0)
rowLabel.setCellValue(label)
rowLabel.cellStyle = styles[CustomCellStyle.RED_BOLD_ARIAL_WITH_BORDER]
}
private fun createStringsRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
val row = sheet.createRow(1)
createRowLabelCell(row, styles, "Strings row")
for (columnNumber in 1 until 5) {
val cell = row.createCell(columnNumber)
cell.setCellValue("String $columnNumber")
cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED]
}
}
private fun createDoublesRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
val row = sheet.createRow(2)
createRowLabelCell(row, styles, "Doubles row")
for (columnNumber in 1 until 5) {
val cell = row.createCell(columnNumber)
cell.setCellValue(BigDecimal("${columnNumber}.99").toDouble())
cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED]
}
}
private fun createDatesRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
val row = sheet.createRow(3)
createRowLabelCell(row, styles, "Dates row")
for (columnNumber in 1 until 5) {
val cell = row.createCell(columnNumber)
cell.setCellValue((LocalDate.now()))
cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED_DATE_FORMAT]
}
}
As you can see, the first thing the generateReport function does is that it styles the initialization. We pass the Workbook instance to the StylesGenerator and in return, we get a map, which we will use later to obtain appropriate CellStyles.
After that, it creates a new sheet within our workbook and passes a name for it.
Then, it invokes functions responsible for setting the columns’ widths and operating on our sheet row by row.
Finally, it writes out our workbook to a ByteArrayOutputStream.
Let’s take a minute and analyze what exactly each function does:
private fun setColumnsWidth(sheet: Sheet) {
sheet.setColumnWidth(0, 256 * 20)
for (columnIndex in 1 until 5) {
sheet.setColumnWidth(columnIndex, 256 * 15)
}
}
As the name suggests, setColumnsWidth is responsible for setting widths of columns in our sheet. The first parameter passed to the setColumnWidth indicates the columnIndex, whereas the second one sets the width (in units of 1/256th of a character width).
private fun createRowLabelCell(row: Row, styles: Map<CustomCellStyle, CellStyle>, label: String) {
val rowLabel = row.createCell(0)
rowLabel.setCellValue(label)
rowLabel.cellStyle = styles[CustomCellStyle.RED_BOLD_ARIAL_WITH_BORDER]
}
The createRowLabelCell function is responsible for adding a cell in the first column of the passed row, alongside setting its value to the specified label and setting the style. I’ve decided to add this function to slightly reduce the code’s redundancy.
All of the below functions are pretty similar. Their purpose is to create a new row, invoking the createRowLabelCell function (except for createHeaderRow) and adding five columns with data to our sheet.
private fun createHeaderRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
val row = sheet.createRow(0)
for (columnNumber in 1 until 5) {
val cell = row.createCell(columnNumber)
cell.setCellValue("Column $columnNumber")
cell.cellStyle = styles[CustomCellStyle.GREY_CENTERED_BOLD_ARIAL_WITH_BORDER]
}
}
private fun createStringsRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
val row = sheet.createRow(1)
createRowLabelCell(row, styles, "Strings row")
for (columnNumber in 1 until 5) {
val cell = row.createCell(columnNumber)
cell.setCellValue("String $columnNumber")
cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED]
}
}
private fun createDoublesRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
val row = sheet.createRow(2)
createRowLabelCell(row, styles, "Doubles row")
for (columnNumber in 1 until 5) {
val cell = row.createCell(columnNumber)
cell.setCellValue(BigDecimal("${columnNumber}.99").toDouble())
cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED]
}
}
private fun createDatesRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
val row = sheet.createRow(3)
createRowLabelCell(row, styles, "Dates row")
for (columnNumber in 1 until 5) {
val cell = row.createCell(columnNumber)
cell.setCellValue((LocalDate.now()))
cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED_DATE_FORMAT]
}
}
Step 5: Implement the REST ReportController
As the last step, we will implement a class named ReportController. It will be responsible for handling POST requests coming to our two REST endpoints:
- /api/report/xlsx — creating a report in a .xlsx format
- /api/report/xls — same as above, but in a .xls format
@RestController
@RequestMapping("/api/report")
class ReportController(
private val reportService: ReportService
) {
@PostMapping("/xlsx")
fun generateXlsxReport(): ResponseEntity<ByteArray> {
val report = reportService.generateXlsxReport()
return createResponseEntity(report, "report.xlsx")
}
@PostMapping("/xls")
fun generateXlsReport(): ResponseEntity<ByteArray> {
val report = reportService.generateXlsReport()
return createResponseEntity(report, "report.xls")
}
private fun createResponseEntity(
report: ByteArray,
fileName: String
): ResponseEntity<ByteArray> =
ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename="$fileName"")
.body(report)
}
The most interesting part of the above code is the createResponseEntity function, which sets the passed ByteArray with its generated report as a response body.
Additionally, we set the Content-Type header of the response as the application/octet-stream, and the Content-Disposition as the attachment; filename=<FILENAME>.
Step 6: Test Everything With Postman
Finally, we can run and test our Spring Boot application, for instance with the gradlew
command:
./gradlew bootRun
By default, the Spring Boot application will be running on port 8080, so let’s open up Postman (or any other tool), specify the POST request to localhost:8080/api/report/xls and hit Send and Download button:
If everything went well, we should see a window allowing us to save the .xls file.
Similarly, let’s test the second endpoint:
This time, the file extension should be .xlsx.
Summary
That’s all for this article! We’ve covered the process of generating Excel reports in a Spring Boot REST API with Apache POI and Kotlin.
If you enjoyed it and would like to learn other topics through similar articles, please visit my blog, Codersee.
And the last thing: for the source code of a fully working project, please refer to this GitHub repository.
Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people get jobs as developers. Get started
ExcelKt
An idiomatic Kotlin wrapper over the Apache POI Excel library for easily generating Excel xlsx files.
Key Features
- Write idiomatic kotlin that looks clean and logical
- Simple style system that allows you to use the Apache POI CellStyle system to stylize workbooks, sheets, rows, or specific cells
- Very lightweight
Installation
Gradle
In your gradle build file add the following:
Kotlin DSL
repositories {
mavenCentral()
}
dependencies {
implementation("io.github.evanrupert:excelkt:1.0.2")
}
Groovy DSL
repositories {
mavenCentral()
}
dependencies {
implementation 'io.github.evanrupert:excelkt:1.0.2'
}
Maven
In your pom.xml
file make sure you have the following in your repositories
:
<repository> <id>mavenCentral</id> <url>https://repo1.maven.org/maven2/</url> </repository>
Then add the following to your dependencies
:
<dependency> <groupId>io.github.evanrupert</groupId> <artifactId>excelkt</artifactId> <version>1.0.2</version> </dependency>
Legacy Installation
For older versions of ExcelKt (v0.1.2
and before), which run on kotlin version 1.3.x
and apache poi version 3.9
, add the following to your gradle build file:
Kotlin DSL
repositories { jcenter() maven(url = "https://jitpack.io") } dependencies { implementation("com.github.EvanRupert:ExcelKt:v0.1.2") }
Groovy DSL
repositories { jcenter() maven { url 'https://jitpack.io' } } dependencies { implementation 'com.github.evanrupert:excelkt:v0.1.2' }
And use import excelkt.*
instead of import io.github.evanrupert.excelkt.*
Quick Example
import io.github.evanrupert.excelkt.* import org.apache.poi.ss.usermodel.FillPatternType import org.apache.poi.ss.usermodel.IndexedColors data class Customer( val id: String, val name: String, val address: String, val age: Int ) fun findCustomers(): List<Customer> = listOf( Customer("1", "Robert", "New York", 32), Customer("2", "Bobby", "Florida", 12) ) fun main() { workbook { sheet { row { cell("Hello, World!") } } sheet("Customers") { customersHeader() for (customer in findCustomers()) row { cell(customer.id) cell(customer.name) cell(customer.address) cell(customer.age) } } }.write("test.xlsx") } fun Sheet.customersHeader() { val headings = listOf("Id", "Name", "Address", "Age") val headingStyle = createCellStyle { setFont(createFont { fontName = "IMPACT" color = IndexedColors.PINK.index }) fillPattern = FillPatternType.SOLID_FOREGROUND fillForegroundColor = IndexedColors.AQUA.index } row(headingStyle) { headings.forEach { cell(it) } } }
Supported cell data types
Cells support the following content types:
- Formula
- Boolean
- Number
- Date
- Calendar
- LocalDate
- LocalDateTime
All other types will be converted to strings.
Example of all data types in use:
row { cell(Formula("A1 + A2")) cell(true) cell(12.2) cell(Date()) cell(Calendar.getInstance()) cell(LocalDate.now()) cell(LocalDateTime.now()) }
Note on Dates
By default, dates will display as numbers in Excel. In order to display them correctly, create a cell style with the dataFormat
set to your preferred format. See the following example:
row { val cellStyle = createCellStyle { dataFormat = xssfWorkbook.creationHelper.createDataFormat().getFormat("m/d/yy h:mm") } cell(Date(), cellStyle) }
The current Apache POI Excel official document (https://poi.apache.org/components/spreadsheet/index.html) explains a mixture of old and new methods, and there are many parts that are difficult to understand, so here is a frequently used description. I summarized it.
Language kotlin
setup
gradle installation
build.gradle
dependencies {
/*Stable version as of August 2020 4.1.2*/
'ApachePOI'
compile("org.apache.poi:poi:4.1.2")
'When creating an xlsx file, the following poi-ooxml required'
compile("org.apache.poi:poi-ooxml:4.1.2")
}
basic operation
Creating a sheet
sample.kt
//Create XSSF Workbook entity
val workBook = XSSFWorkbook()
//Create an excel sheet
val sheet = workBook.createSheet()
Enter the value in the specified cell
sample.kt
//Specify the cell to enter the value
//Specify a column with createRow
val row = sheet.createRow(0)
//Specify a row with createCell
val cell = row.createCell(0)
//Fill in the value
cell.setCellValue("test")
Save sheet
sample.kt
val fileOutputStream = FileOutputStream("test.xlsx")
workBook.write(fileOutputStream)
fileOutputStream.close()
result
Applied operation
Cell color specification
sample.kt
//Create a style instance
val style = workBook.createCellStyle()
//Set cell to yellow
style.fillForegroundColor = (XSSFColor(byteArrayOf(255.toByte(), 255.toByte(), 204.toByte()), null)
//Specify fill
style.fillPattern = FillPatternType.SOLID_FOREGROUND
//Style the cell
cell.setCellStyle(style)
result
Font settings
sample.kt
//Create a font instance
val font = workBook.createFont()
//Set font size
font.setFontHeightInPoints(16.toShort())
//Set character type
font.fontName = "MS Gothic"
val style = workBook.createCellStyle()
//Add font information to style
style.setFont(font)
Conditional formatting
List of conditions that can be set
https://github.com/apache/poi/blob/trunk/src/java/org/apache/poi/ss/usermodel/ComparisonOperator.java
sample.kt
val sheet = workBook.createSheet()
//Create an instance for conditional formatting
val sheetCF: SheetConditionalFormatting = sheet.sheetConditionalFormatting
//Set conditions: In this case, the value is 90%The following
val rule1 = sheetCF.createConditionalFormattingRule(ComparisonOperator.LT, "90%")
//Set the font to be used under the above conditions(Various elements other than fonts can be set)
val font = rule1.createFontFormatting()
//Set font to red
font.fontColor = XSSFColor(byteArrayOf(255.toByte(), 0.toByte(), 0.toByte()), null)
//Set the range of cells for which the above conditions are valid(In the following cases, from cell A1 to cell A5)
val range = "A1:A5"
//Multiple ranges can be set in the array
val regions = arrayOf(
CellRangeAddress.valueOf(range)
)
//Enable condition
sheetCF.addConditionalFormatting(regions, rule1)
Set formula in cell
sample.kt
val sheet = workBook.createSheet()
val row = sheet.createRow(0)
val cell = row.createCell(0)
//A1 cell/Create a formula that gives the percentage of C1 cells
val formula = "A1/C1"
//Set formula in cell
cell.cellFormula = formula
//Enable formulas
sheet.setForceFormulaRecalculation(true)