Compartir en Twitter
Go to Homepage

GENERAR REPORTES EXCEL EN SPRING BOOT REST

October 27, 2025

Introducción a la generación de reportes Excel

La generación de reportes en formatos Excel, como .xls y .xlsx, es una necesidad común en aplicaciones empresariales. En este tutorial, exploraremos cómo implementar una API REST en Spring Boot utilizando Kotlin y la biblioteca Apache POI para crear reportes Excel con estilos personalizados. Aprenderás a configurar un proyecto, definir estilos de celdas, generar archivos en ambos formatos y exponerlos mediante endpoints REST. Este enfoque es ideal para desarrolladores que buscan integrar capacidades de generación de reportes en aplicaciones modernas.

Configuración inicial del proyecto

Para comenzar, crea un proyecto Spring Boot utilizando Spring Initializr. Selecciona Kotlin como lenguaje, Spring Boot 3.x como versión, y asegúrate de incluir las dependencias necesarias. Las bibliotecas requeridas son:

  • Spring Boot Starter Web: Permite crear una API REST.
  • Apache POI: Biblioteca para manipular archivos Excel (.xls).
  • Apache POI-OOXML: Extensión para trabajar con el formato .xlsx (Open XML).

Agrega las siguientes dependencias al archivo build.gradle.kts:

implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.apache.poi:poi:5.3.0")
implementation("org.apache.poi:poi-ooxml:5.3.0")

Actualizamos a la versión 5.3.0 de Apache POI, disponible en octubre de 2025, para aprovechar las últimas mejoras. Ejecuta el comando para sincronizar el proyecto:

./gradlew build

Asegúrate de tener configurado un entorno con Java 17 o superior, ya que Spring Boot 3.x lo requiere.

Definición de estilos personalizados

La personalización de celdas es fundamental para crear reportes visualmente atractivos. Para organizar los estilos, definimos un enum class en Kotlin que representa diferentes formatos de celdas. Esto facilita la reutilización y mantenimiento del código.

Crea una clase enum llamada CustomCellStyle:

enum class CustomCellStyle {
    GREY_CENTERED_BOLD_ARIAL_WITH_BORDER,
    RIGHT_ALIGNED,
    RED_BOLD_ARIAL_WITH_BORDER,
    RIGHT_ALIGNED_DATE_FORMAT
}

Este enum servirá como clave para un mapa de estilos que aplicaremos a las celdas. Cada constante representa un formato específico, como texto centrado en gris o fechas alineadas a la derecha.

Generación de estilos con Apache POI

Apache POI proporciona la interfaz CellStyle para definir estilos de celdas, como alineación, bordes y fuentes. Creamos una clase StylesGenerator para centralizar la lógica de creación de estilos.

import org.apache.poi.ss.usermodel.*
import org.springframework.stereotype.Component

@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
        )
    }

    private fun createBoldArialFont(wb: Workbook): Font {
        val font = wb.createFont()
        font.fontName = "Arial"
        font.bold = true
        return font
    }

    private fun createRedBoldArialFont(wb: Workbook): Font {
        val font = wb.createFont()
        font.fontName = "Arial"
        font.bold = true
        font.color = IndexedColors.RED.index
        return font
    }

    private fun createRightAlignedStyle(wb: Workbook): CellStyle {
        val style = wb.createCellStyle()
        style.alignment = HorizontalAlignment.RIGHT
        return style
    }

    private fun createBorderedStyle(wb: Workbook): CellStyle {
        val thin = BorderStyle.THIN
        val black = IndexedColors.BLACK.index
        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.index
        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 = wb.createDataFormat().getFormat("dd/mm/yyyy")
        return style
    }
}

El método prepareStyles genera un mapa que asocia cada constante de CustomCellStyle con un objeto CellStyle. Usamos fuentes personalizadas en Kotlin y definimos estilos como texto alineado a la derecha, bordes negros y fondo gris. El formato de fecha se ajusta al estándar dd/mm/yyyy para mayor claridad.

Implementación del servicio de reportes

El núcleo de la generación de reportes reside en la clase ReportService, que crea archivos .xls y .xlsx y los devuelve como arreglos de bytes. Esta clase utiliza las implementaciones XSSFWorkbook para .xlsx y HSSFWorkbook para .xls.

Crea la clase ReportService:

import org.apache.poi.hssf.usermodel.HSSFWorkbook
import org.apache.poi.ss.usermodel.*
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.springframework.stereotype.Service
import java.io.ByteArrayOutputStream
import java.math.BigDecimal
import java.time.LocalDate

@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)
    }

    private fun generateReport(wb: Workbook): ByteArray {
        val styles = stylesGenerator.prepareStyles(wb)
        val sheet: Sheet = wb.createSheet("Reporte Ejemplo")

        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("Columna $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, "Fila de textos")
        for (columnNumber in 1 until 5) {
            val cell = row.createCell(columnNumber)
            cell.setCellValue("Texto $columnNumber")
            cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED]
        }
    }

    private fun createDoublesRow(sheet: Sheet, styles: Map<CustomCellStyle, CellStyle>) {
        val row = sheet.createRow(2)
        createRowLabelCell(row, styles, "Fila de números")
        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, "Fila de fechas")
        for (columnNumber in 1 until 5) {
            val cell = row.createCell(columnNumber)
            cell.setCellValue(LocalDate.now())
            cell.cellStyle = styles[CustomCellStyle.RIGHT_ALIGNED_DATE_FORMAT]
        }
    }
}

El método generateReport crea una hoja Excel, aplica estilos y llena filas con datos de ejemplo: encabezados, cadenas, números y fechas. La función setColumnsWidth ajusta los anchos de columna en unidades de 1/256 de un carácter. Cada fila utiliza estilos predefinidos para garantizar consistencia.

Creación del controlador REST

Para exponer los reportes a través de una API REST, implementamos la clase ReportController. Esta clase define dos endpoints: uno para .xlsx y otro para .xls.

import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api/report")
class ReportController(
    private val reportService: ReportService
) {
    @PostMapping("/xlsx")
    fun generateXlsxReport(): ResponseEntity<ByteArray> {
        val report = reportService.generateXlsxReport()
        return createResponseEntity(report, "reporte.xlsx")
    }

    @PostMapping("/xls")
    fun generateXlsReport(): ResponseEntity<ByteArray> {
        val report = reportService.generateXlsReport()
        return createResponseEntity(report, "reporte.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)
}

El método createResponseEntity configura la respuesta HTTP con el tipo de contenido application/octet-stream y un encabezado Content-Disposition para indicar que el archivo debe descargarse. Esto permite que los clientes descarguen los reportes como archivos Excel descargables.

Pruebas con Postman

Para verificar que la API funciona correctamente, ejecuta la aplicación Spring Boot con el siguiente comando:

./gradlew bootRun

La aplicación se ejecutará en el puerto 8080 por defecto. Usa Postman para realizar solicitudes POST a los endpoints:

  • /api/report/xlsx: Descarga un archivo .xlsx.
  • /api/report/xls: Descarga un archivo .xls.

Configura Postman para enviar una solicitud POST a http://localhost:8080/api/report/xlsx. Haz clic en “Send and Download” para guardar el archivo. Repite el proceso para el endpoint .xls. Los archivos generados contendrán una hoja con encabezados en gris, etiquetas en rojo y datos alineados a la derecha.

Optimización y mejores prácticas

Para mejorar el código, considera las siguientes prácticas:

  • Reutilización de estilos: Almacenar estilos en un mapa reduce la creación redundante de objetos CellStyle, mejorando el rendimiento.
  • Manejo de errores: Agrega excepciones personalizadas para manejar errores al generar o escribir el archivo Excel.
  • Configuración dinámica: Permite que los usuarios especifiquen nombres de hojas o formatos de datos a través de parámetros en la solicitud POST.
  • Validación de datos: Antes de generar el reporte, valida los datos de entrada para evitar celdas vacías o formatos incorrectos.

Por ejemplo, para manejar errores, puedes modificar el método generateReport:

private fun generateReport(wb: Workbook): ByteArray {
    try {
        val styles = stylesGenerator.prepareStyles(wb)
        val sheet: Sheet = wb.createSheet("Reporte Ejemplo")
        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()
    } catch (e: Exception) {
        throw RuntimeException("Error al generar el reporte Excel: ${e.message}")
    }
}

Escalabilidad para reportes complejos

Para reportes más complejos, como aquellos con múltiples hojas o gráficos, Apache POI ofrece funcionalidades avanzadas. Por ejemplo, para agregar una segunda hoja:

val secondSheet = wb.createSheet("Hoja Adicional")
secondSheet.setColumnWidth(0, 256 * 25)
val row = secondSheet.createRow(0)
val cell = row.createCell(0)
cell.setCellValue("Datos adicionales")
cell.cellStyle = styles[CustomCellStyle.RED_BOLD_ARIAL_WITH_BORDER]

Esto permite escalar la solución para incluir más datos o formatos. Además, considera usar plantillas Excel predefinidas para reportes con diseños complejos, cargándolas con XSSFWorkbook y modificando solo los datos necesarios.

Conclusiones

Hemos explorado cómo generar reportes Excel en una API REST con Spring Boot, Kotlin y Apache POI. Desde la configuración del proyecto hasta la creación de estilos personalizados y endpoints REST, este tutorial proporciona una base sólida para implementar reportes en aplicaciones empresariales. La combinación de enums para estilos, una clase de servicio modular y un controlador REST eficiente garantiza un código mantenible y escalable. Para proyectos más avanzados, considera integrar validaciones, manejo de errores robusto y soporte para plantillas predefinidas. Con estas herramientas, puedes generar reportes Excel profesionales adaptados a tus necesidades.