<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ sprint boot - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Descubre miles de cursos de programación escritos por expertos. Aprende Desarrollo Web, Ciencia de Datos, DevOps, Seguridad y obtén asesoramiento profesional para desarrolladores. ]]>
        </description>
        <link>https://www.freecodecamp.org/espanol/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ sprint boot - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/espanol/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 18 Jun 2026 04:56:07 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/espanol/news/tag/sprint-boot/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Cómo generar un Reporte de Excel en una API REST de Spring Boot con Apache POI y Kotlin ]]>
                </title>
                <description>
                    <![CDATA[ En este artículo, me gustaría mostrarte cómo generar reportes Excel en los formatos .xls y .xlsx (también conocidos como Open XML) en una API REST de Spring Boot con Apache POI y Kotlin. Después del término de esta guía, tendrás un entendimiento fundamental sobre cómo crear de forma personalizada, formatos ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/generar-reporte-excel-en-rest-api-spring/</link>
                <guid isPermaLink="false">63e982f622450a0629adc3eb</guid>
                
                    <category>
                        <![CDATA[ sprint boot ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Josué Leiva ]]>
                </dc:creator>
                <pubDate>Tue, 21 Feb 2023 14:31:57 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2023/02/New-Banner.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Artículo original:</strong> <a href="https://www.freecodecamp.org/news/generate-excel-report-in-spring-rest-api/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Generate an Excel Report in a Spring Boot REST API with Apache POI and Kotlin</a>
      </p><p>En este artículo, me gustaría mostrarte cómo generar reportes Excel en los formatos <strong>.xls</strong> y <strong>.xlsx </strong>(también conocidos como Open XML) en una <strong>API REST de Spring Boot</strong> con <strong>Apache POI y Kotlin</strong>.</p><p>Después del término de esta guía, tendrás un entendimiento fundamental sobre cómo crear de forma personalizada, formatos de celdas, estilos y fuentes. Al final, te mostraré cómo crear endpoints para que así puedas descargar los archivos generados fácilmente.</p><p>Para visualizar mejor lo que aprenderemos, revisa la vista previa del resultado final.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/12/result_file_preview.png" class="kg-image" alt="result_file_preview" width="600" height="400" loading="lazy"></figure><h2 id="paso-1-a-adir-las-dependencias-necesarias-"><strong>Paso 1: Añadir las dependencias necesarias.</strong></h2><p>Como primer paso, vamos a crear un proyecto Spring Boot (Recomiendo altamente, utilizar la página <a href="https://start.spring.io/">Spring Initializr</a>) e importar las siguientes dependencias.</p><pre><code class="language-groovy">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")</code></pre><p>Déjame explicar el propósito de cada librería.</p><ul><li>El <strong><strong>Spring Boot Starter Web</strong></strong> es necesario para crear la API REST en nuestra aplicación.</li><li><strong>Apache POI</strong> es una librería compleja de Java para trabajar con archivos Excel. Si quisiéramos trabajar solo con el formato <strong>.xls</strong>, entonces la dependencia <em>poi</em> sería suficiente. En nuestro caso, queremos añadir soporte para el formato <strong>.xlsx</strong>, por lo que el componente<em> poi-ooxml</em>, también es necesario.</li></ul><h2 id="paso-2-crear-los-modelos"><strong>Paso 2: Crear los modelos</strong></h2><p>Como siguiente paso, vamos a crear una clase tipo enum llamada <strong>CustomCellStyle</strong> con 4 constantes:</p><pre><code class="language-kotlin">enum class CustomCellStyle {
    GREY_CENTERED_BOLD_ARIAL_WITH_BORDER,
    RIGHT_ALIGNED,
    RED_BOLD_ARIAL_WITH_BORDER,
    RIGHT_ALIGNED_DATE_FORMAT
}</code></pre><p>Si bien el propósito de esta clase enum pueda verse un tanto enigmática al momento, se volverá clara en las siguientes secciones.</p><h2 id="paso-3-preparar-los-estilos-de-celdas"><strong>Paso 3: Preparar los estilos de celdas</strong></h2><p>La librería Apache POI viene con la interfaz <strong>CellStyle</strong>, la cual podemos utilizar para definir estilos y formatos personalizados entre filas, columnas y celdas.</p><p>Vamos a crear un componente <strong>StylesGenerator</strong>, el cual será responsable de preparar un mapa que contenga nuestros estilos personalizados.</p><pre><code class="language-kotlin">@Component
class StylesGenerator {

    fun prepareStyles(wb: Workbook): Map&lt;CustomCellStyle, CellStyle&gt; {
        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
        )
    }
}</code></pre><p>Como puedes ver, con este acercamiento, creamos cada estilo una vez y lo ponemos dentro de un mapa para que así lo podamos referenciar después.</p><p>Hay bastantes técnicas de diseño que podríamos utilizar aquí, pero creo que utilizando un mapa y constantes enum es una de las mejores maneras para mantener el código más limpio y más fácil de modificar.</p><p>Dicho esto, añadamos algunas funciones faltantes dentro de la clase generadora. Comencemos primero con las fuentes personalizadas:</p><pre><code class="language-kotlin">private fun createBoldArialFont(wb: Workbook): Font {
    val font = wb.createFont()
    font.fontName = "Arial"
    font.bold = true
    return font
}</code></pre><p>La función <strong><strong>createBoldArialFont</strong></strong> crea una nueva instancia en negrita de la fuente Arial, la cual la usaremos más tarde.</p><p>Similarmente, vamos a implementar una función <strong><strong>createRedBoldArialFont</strong></strong> y establecer el color de la fuente a rojo:</p><pre><code class="language-kotlin">private fun createRedBoldArialFont(wb: Workbook): Font {
    val font = wb.createFont()
    font.fontName = "Arial"
    font.bold = true
    font.color = IndexedColors.RED.index
    return font
}</code></pre><p>Después de eso, podemos añadir otras funciones responsables de crear instancias <strong>CellStyle</strong> individuales.</p><pre><code class="language-kotlin">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
}</code></pre><p>Por favor hay que tener en mente que los ejemplos de arriba son solo una parte pequeña de las posibilidades de <strong>CellStyle </strong>(estilo de celda). Si te gustaría ver una lista completa, por favor refiérate a la documentación <a href="https://poi.apache.org/apidocs/dev/org/apache/poi/ss/usermodel/CellStyle.html">aquí</a> (artículo en inglés).</p><h2 id="paso-4-crear-la-clase-reportservice"><strong>Paso 4: Crear la clase ReportService</strong></h2><p>Como siguiente paso vamos a implementar la clase <strong>ReportService</strong> responsable de crear los archivos <strong>.xlsx</strong> y <strong>.xls</strong> y retornarlos como instancias ByteArray:</p><pre><code class="language-kotlin">@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)
    }
 }   </code></pre><p>Como puedes ver, la única diferencia entre la generación de estos dos formatos es el tipo de implementación del <strong>Workbook</strong> (libro de trabajo) que hemos utilizado. Para el formato .xlsx vamos a utilizar la clase <strong><strong>XSSFWorkbook</strong></strong>, y para el .xls utilizaremos <strong><strong>HSSFWorkbook</strong></strong>.</p><p>Vamos a añadir el resto del código a <strong>ReportService</strong>:</p><pre><code class="language-kotlin">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&lt;CustomCellStyle, CellStyle&gt;) {
    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&lt;CustomCellStyle, CellStyle&gt;, 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&lt;CustomCellStyle, CellStyle&gt;) {
    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&lt;CustomCellStyle, CellStyle&gt;) {
    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&lt;CustomCellStyle, CellStyle&gt;) {
    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]
    }
}</code></pre><p>Como puedes ver, la primera cosa que la función <strong>generateReport</strong> hace, es que estiliza la inicialización. Pasamos la instancia <strong>Workbook</strong> al <strong>StylesGenerator</strong> (generador de estilos) y en retorno, obtenemos un mapa, el cual utilizaremos más tarde para obtener CellStyles (estilos de celda) apropiados.</p><p>Después de esto, crea una nueva hoja dentro de nuestro libro de trabajo y le entrega un nombre para este (el libro).</p><p>Después, invoca las funciones responsables de configurar el ancho de las columnas y operar nuestra hoja fila por fila.</p><p>Finalmente, escribe nuestro libro de trabajo a un ByteArrayOutputStream.</p><p>Tomemos un minuto y analicemos que hace exactamente cada función:</p><pre><code class="language-kotlin">private fun setColumnsWidth(sheet: Sheet) {
    sheet.setColumnWidth(0, 256 * 20)

    for (columnIndex in 1 until 5) {
        sheet.setColumnWidth(columnIndex, 256 * 15)
    }
}</code></pre><p>Como el nombre sugiere, <strong><strong>setColumnsWidth</strong></strong> (establecerAnchoColumnas) es responsable de configurar los anchos de las columnas en nuestra hoja. El primer parámetro pasado a &nbsp;<strong><strong>setColumnsWidth</strong></strong> indica el columnIndex (indiceColumna), y el segundo establece el ancho (en unidades de 1/256 de ancho de carácter).</p><pre><code class="language-kotlin">private fun createRowLabelCell(row: Row, styles: Map&lt;CustomCellStyle, CellStyle&gt;, label: String) {
    val rowLabel = row.createCell(0)
    rowLabel.setCellValue(label)
    rowLabel.cellStyle = styles[CustomCellStyle.RED_BOLD_ARIAL_WITH_BORDER]
}</code></pre><p>La función <strong><strong>createRowLabelCell</strong></strong> (crearEtiquetaFilaCelda) es responsable de añadir una celda en la primera columna de la fila entregada, paralelamente configura su valor a la etiqueta especificada y configura su estilo. He decidido añadir esta función para reducir un poco la redundacia del código.</p><p>Todas las funciones de abajo son bastante similares. Su propósito es crear una nueva fila, invocar la función <strong><strong>createRowLabelCell</strong></strong> (a excepción de <strong><strong>createHeaderRow</strong></strong><em>) </em>y añadir cinco columnas con información a nuestra hoja.</p><pre><code class="language-kotlin">private fun createHeaderRow(sheet: Sheet, styles: Map&lt;CustomCellStyle, CellStyle&gt;) {
    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]
    }
}</code></pre><pre><code class="language-kotlin">private fun createStringsRow(sheet: Sheet, styles: Map&lt;CustomCellStyle, CellStyle&gt;) {
    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]
    }
}</code></pre><pre><code class="language-kotlin">private fun createDoublesRow(sheet: Sheet, styles: Map&lt;CustomCellStyle, CellStyle&gt;) {
    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]
    }
}</code></pre><pre><code class="language-kotlin">private fun createDatesRow(sheet: Sheet, styles: Map&lt;CustomCellStyle, CellStyle&gt;) {
    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]
    }
}</code></pre><h2 id="paso-5-implementar-el-reportcontroller-rest">Paso 5: Implementar el ReportController REST</h2><p>Como último paso, vamos a implementar una clase llamada <strong><strong>ReportController</strong></strong>. Será responsable de manejar las peticiones POST entrantes a nuestros dos endpoints REST:</p><ul><li><em><em>/api/report/xlsx </em>- </em>crear un reporte en formato <em>.xlsx</em></li><li><em><em>/api/report/xls</em></em> - lo mismo de arriba, pero en formato <em>.xls</em></li></ul><pre><code class="language-kotlin">@RestController
@RequestMapping("/api/report")
class ReportController(
    private val reportService: ReportService
) {

    @PostMapping("/xlsx")
    fun generateXlsxReport(): ResponseEntity&lt;ByteArray&gt; {
        val report = reportService.generateXlsxReport()

        return createResponseEntity(report, "report.xlsx")
    }

    @PostMapping("/xls")
    fun generateXlsReport(): ResponseEntity&lt;ByteArray&gt; {
        val report = reportService.generateXlsReport()

        return createResponseEntity(report, "report.xls")
    }

    private fun createResponseEntity(
        report: ByteArray,
        fileName: String
    ): ResponseEntity&lt;ByteArray&gt; =
        ResponseEntity.ok()
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"$fileName\"")
            .body(report)

}</code></pre><p>La parte más interesante del código de arriba es la función <strong><strong>createResponseEntity</strong></strong> (crearEntidadRespuesta), la cual establece el ByteArray (Arreglo de Bytes) entregado con su reporte generado como un cuerpo de respuesta.</p><p>Adicionalmente, establecemos el encabezado <strong><strong>Content</strong>-Type</strong> de la respuesta como <strong><strong>application/octet-stream</strong></strong>, y la disposición de contenido (Content-Disposition) como el archivo adjunto <strong><strong>attachment;</strong> filename = &lt;FILENAME&gt;</strong></p><h2 id="paso-6-testear-t-do-con-postman">Paso 6: Testear tódo con Postman</h2><p>Finalmente, podemos correr y probar nuetra aplicación Spring Boot, por ejemplo con el commando <code>gradlew</code>:</p><pre><code>./gradlew bootRun</code></pre><p>Por defecto, la aplicación Spring Boot estará corriendo en el puerto 8080, así que vamos a abrir <a href="https://www.postman.com/">Postman</a> (o cualquier otra herramienta), especificar la petición <strong>POST</strong> a <strong><strong>localhost:8080/api/report/xls</strong></strong> &nbsp;y presionar el botón <strong><strong>Send and Download</strong></strong> (enviar y descargar):</p><p>Si tódo estuvo bien , tendríamos que ver una ventana que nos permita guardar el archivo <strong>.xls</strong>.</p><p>Similarmente, vamos a probar el segundo endpoint:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2020/12/POST_xlsx.png" class="kg-image" alt="POST_xlsx" width="600" height="400" loading="lazy"></figure><p>Esta vez, la extensión del archivo tendría que ser <strong>.xlsx</strong>.</p><h2 id="resumen">Resumen</h2><p>¡Eso es todo por este artículo! Hemos cubierto el proceso de generar reportes Excel en una API REST de Spring Boot con Apache POI y Kotlin.</p><p>Si lo disfrutaste y te gustaría aprender otros temas a través de artículo similares, porfavor visita my blog <a href="https://codersee.com/"><strong><strong>Codersee</strong></strong></a><strong> </strong>(blog en inglés).</p><p>Y la última cosa: para el código fuente, o un proyecto completamente funcional, porfavor consulte <a href="https://github.com/codersee-blog/freecodecamp-spring-boot-kotlin-excel">este repositorio GitHub</a> (en inglés).</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
