Close

Spring MVC - Excel View Example

[Last Updated: Oct 8, 2026]

This example demonstrates how to use an Excel document as the view technology in a Spring MVC application. Spring integrates with the Apache POI library, which is used for reading and writing Microsoft Office document formats.

In this example, we are going to use AbstractXlsView, a convenient base class for Excel views in the legacy binary XLS format. For the newer XLSX format, Spring provides the subclasses AbstractXlsxView and AbstractXlsxStreamingView.

Deprecation notice: As of Spring Framework 7.0, the view classes in the org.springframework.web.servlet.view.document package, including AbstractXlsView, are deprecated. AbstractXlsxView and AbstractXlsxStreamingView are explicitly marked for removal, which the Spring team plans for version 7.1 or 7.2. The approach shown here still works in Spring 6.x, and in 7.x with deprecation warnings. For new code, see the Alternative Without Document Views section at the end of this tutorial.

Example

Additional Maven Dependency

pom.xml

<dependency>
   <groupId>org.apache.poi</groupId>
   <artifactId>poi</artifactId>
   <version>5.5.1</version>
</dependency>

This example generates the legacy .xls format, so the core poi artifact is sufficient. To generate .xlsx files, use poi-ooxml instead.

Java Config class

In this example, we are going to use BeanNameViewResolver for resolving the Excel view. That means we need to register an implementation of AbstractXlsView as a bean. The bean name will be used as the view name returned from the controller. Also check out this simple example of BeanNameViewResolver.

package com.logicbig.example;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.BeanNameViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@EnableWebMvc
@Configuration
@ComponentScan
public class MyWebConfig implements WebMvcConfigurer {
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        BeanNameViewResolver resolver = new BeanNameViewResolver();
        resolver.setOrder(0);
        registry.viewResolver(resolver);

        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/views/");
        viewResolver.setSuffix(".jsp");
        viewResolver.setOrder(1);
        registry.viewResolver(viewResolver);
    }
}

Implementing AbstractXlsView

package com.logicbig.example;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.document.AbstractXlsView;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.List;
import java.util.Map;

@Component("forexView")
public class ForexExcelView extends AbstractXlsView {

    @Override
    protected void buildExcelDocument(Map<String, Object> model,
                                      Workbook workbook,
                                      HttpServletRequest request,
                                      HttpServletResponse response)
            throws Exception {
        List<CurrencyRate> currencyRates = (List<CurrencyRate>) model.get(
                "todayCurrencyRates");
        Sheet sheet = workbook.createSheet("Today Currency Rates");
        sheet.setFitToPage(true);

        int rowCount = 0;
        Row header = sheet.createRow(rowCount++);
        header.createCell(0).setCellValue("Currency Pair");
        header.createCell(1).setCellValue("Bid Price");
        header.createCell(2).setCellValue("Ask Price");
        header.createCell(3).setCellValue("Date");

        for (CurrencyRate currencyRate : currencyRates) {
            Row currencyRow = sheet.createRow(rowCount++);
            currencyRow.createCell(0)
                       .setCellValue(currencyRate.getCurrencyPair());
            currencyRow.createCell(1)
                       .setCellValue(currencyRate.getBidPrice().doubleValue());
            currencyRow.createCell(2)
                       .setCellValue(currencyRate.getAskPrice().doubleValue());
            currencyRow.createCell(3).setCellValue(
                    currencyRate.getDateTime().format(
                            DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)));
        }
        response.setHeader("Content-Disposition",
                           "attachment; filename=forex-rates.xls");
    }
}
package com.logicbig.example;

import java.math.BigDecimal;
import java.time.LocalDateTime;

public class CurrencyRate {
    private String currencyPair;
    private LocalDateTime dateTime;
    private BigDecimal askPrice;
    private BigDecimal bidPrice;
    .............
}

Writing a Spring Controller

package com.logicbig.example;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Currency;
import java.util.List;

@Controller
@RequestMapping("/")
public class MyController {

    @GetMapping("/")
    public String mainView() {
        return "main";
    }

    @GetMapping("/exchangeRates")
    public String handleForexRequest(Model model) {
        model.addAttribute("todayCurrencyRates",
                           getTodayForexRates());
        return "forexView";
    }

    private List<CurrencyRate> getTodayForexRates() {
        //dummy rates
        List<CurrencyRate> currencyRates = new ArrayList<>();
        LocalDateTime today = LocalDateTime.now();
        List<Currency> currencies =
                new ArrayList<>(Currency.getAvailableCurrencies());

        for (int i = 0; i + 1 < currencies.size(); i += 2) {
            String currencyPair = currencies.get(i) + "/" + currencies.get(i + 1);
            CurrencyRate cr = new CurrencyRate();
            cr.setCurrencyPair(currencyPair);
            cr.setDateTime(today);
            BigDecimal bidPrice = new BigDecimal(Math.random() * 5 + 1);
            bidPrice = bidPrice.setScale(3, RoundingMode.CEILING);
            cr.setBidPrice(bidPrice);
            BigDecimal askPrice = new BigDecimal(bidPrice.doubleValue() +
                                                         Math.random() * 2 + 0.5);
            askPrice = askPrice.setScale(3, RoundingMode.CEILING);
            cr.setAskPrice(askPrice);

            currencyRates.add(cr);
        }
        return currencyRates;
    }
}

JSP page

src/main/webapp/WEB-INF/views/main.jsp

<html>
<body>
<h2>Spring Excel View Example</h2>
<a href="/exchangeRates">Get Today Exchange Rates</a>

</body>
</html>

Output

Clicking the link opens the generated spreadsheet:

Tests

package com.logicbig.example;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import java.io.ByteArrayInputStream;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration(classes = MyWebConfig.class)
public class ExcelViewTest {

    @Autowired
    private WebApplicationContext webApplicationContext;

    private MockMvc mockMvc;

    @BeforeEach
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
                                 .build();
    }

    @Test
    public void mainPageForwardsToJsp() throws Exception {
        mockMvc.perform(get("/"))
               .andExpect(status().isOk())
               .andExpect(view().name("main"))
               .andExpect(forwardedUrl("/WEB-INF/views/main.jsp"));
    }

    @Test
    public void exchangeRatesIsDownloadedAsXlsAttachment() throws Exception {
        mockMvc.perform(get("/exchangeRates"))
               .andExpect(status().isOk())
               .andExpect(view().name("forexView"))
               .andExpect(content().contentType("application/vnd.ms-excel"))
               .andExpect(header().string("Content-Disposition",
                                          is("attachment; filename=forex-rates.xls")));
    }

    @Test
    public void workbookBasicTest() throws Exception {
        Sheet sheet = downloadSheet();
        assertThat(sheet.getSheetName(), is("Today Currency Rates"));
        assertThat(cellTexts(sheet.getRow(0)),
                   contains("Currency Pair", "Bid Price", "Ask Price", "Date"));
        assertThat(sheet.getLastRowNum(), greaterThan(0));
    }

    @Test
    public void everyDataRowIsWellFormed() throws Exception {
        StreamSupport.stream(downloadSheet().spliterator(), false)
                     .skip(1)
                     .forEach(row -> {
                         assertThat(Arrays.asList(row.getCell(0)
                                                     .getStringCellValue()
                                                     .split("/")),
                                    hasSize(2));
                         assertThat(row.getCell(1).getNumericCellValue(),
                                    allOf(greaterThanOrEqualTo(1.0),
                                          lessThanOrEqualTo(6.0)));
                         assertThat(row.getCell(2).getNumericCellValue(),
                                    greaterThan(row.getCell(1)
                                                   .getNumericCellValue()));
                         assertThat(row.getCell(3).getStringCellValue(),
                                    not(isEmptyOrNullString()));
                     });
    }

    private Sheet downloadSheet() throws Exception {
        return new HSSFWorkbook(new ByteArrayInputStream(
                mockMvc.perform(get("/exchangeRates"))
                       .andExpect(status().isOk())
                       .andReturn()
                       .getResponse()
                       .getContentAsByteArray())).getSheetAt(0);
    }

    private static List<String> cellTexts(Row row) {
        return StreamSupport.stream(row.spliterator(), false)
                            .map(cell -> cell.getStringCellValue())
                            .collect(Collectors.toList());
    }
}
mvn clean test -Dtest="ExcelViewTest"

Output (filtered)

$ mvn clean test -Dtest="ExcelViewTest"
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.logicbig.example.ExcelViewTest
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.912 s -- in com.logicbig.example.ExcelViewTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.107 s
[INFO] Finished at: 2026-10-06T04:36:01-05:00
[INFO] ------------------------------------------------------------------------

Alternative Without Document Views

Since the document view classes are deprecated, the Spring team recommends rendering the document directly in a web handler, or using a library that provides its own View implementation. A minimal handler-based approach using Apache POI (requires poi-ooxml) looks like this:

@Controller
public class ForexController {

    @GetMapping("/forex.xlsx")
    public void forex(HttpServletResponse response) throws IOException {
        response.setContentType(
           "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, 
                                    "attachment; filename=\"forex.xlsx\"");
        try (XSSFWorkbook workbook = new XSSFWorkbook()) {
            workbook.createSheet("Rates").createRow(0).createCell(0)
                                               .setCellValue("Currency");
            workbook.write(response.getOutputStream());
        }
    }
}

This approach does not depend on any Spring view infrastructure, so it keeps working regardless of when the deprecated classes are removed. Also note that the XLS format is a legacy format. Prefer XLSX unless you must support very old Excel versions.

Example Project

Dependencies and Technologies Used:

  • spring-webmvc 7.0.6 (Spring Web MVC)
     Version Compatibility: 4.2.0.RELEASE - 7.0.6Version List
    ×

    Version compatibilities of spring-webmvc with this example:

      javax.servlet-api:3.x
    • 4.2.0.RELEASE
    • 4.2.1.RELEASE
    • 4.2.2.RELEASE
    • 4.2.3.RELEASE
    • 4.2.4.RELEASE
    • 4.2.5.RELEASE
    • 4.2.6.RELEASE
    • 4.2.7.RELEASE
    • 4.2.8.RELEASE
    • 4.2.9.RELEASE
    • 4.3.0.RELEASE
    • 4.3.1.RELEASE
    • 4.3.2.RELEASE
    • 4.3.3.RELEASE
    • 4.3.4.RELEASE
    • 4.3.5.RELEASE
    • 4.3.6.RELEASE
    • 4.3.7.RELEASE
    • 4.3.8.RELEASE
    • 4.3.9.RELEASE
    • 4.3.10.RELEASE
    • 4.3.11.RELEASE
    • 4.3.12.RELEASE
    • 4.3.13.RELEASE
    • 4.3.14.RELEASE
    • 4.3.15.RELEASE
    • 4.3.16.RELEASE
    • 4.3.17.RELEASE
    • 4.3.18.RELEASE
    • 4.3.19.RELEASE
    • 4.3.20.RELEASE
    • 4.3.21.RELEASE
    • 4.3.22.RELEASE
    • 4.3.23.RELEASE
    • 4.3.24.RELEASE
    • 4.3.25.RELEASE
    • 4.3.26.RELEASE
    • 4.3.27.RELEASE
    • 4.3.28.RELEASE
    • 4.3.29.RELEASE
    • 4.3.30.RELEASE
    • 5.0.0.RELEASE
    • 5.0.1.RELEASE
    • 5.0.2.RELEASE
    • 5.0.3.RELEASE
    • 5.0.4.RELEASE
    • 5.0.5.RELEASE
    • 5.0.6.RELEASE
    • 5.0.7.RELEASE
    • 5.0.8.RELEASE
    • 5.0.9.RELEASE
    • 5.0.10.RELEASE
    • 5.0.11.RELEASE
    • 5.0.12.RELEASE
    • 5.0.13.RELEASE
    • 5.0.14.RELEASE
    • 5.0.15.RELEASE
    • 5.0.16.RELEASE
    • 5.0.17.RELEASE
    • 5.0.18.RELEASE
    • 5.0.19.RELEASE
    • 5.0.20.RELEASE
    • 5.1.0.RELEASE
    • 5.1.1.RELEASE
    • 5.1.2.RELEASE
    • 5.1.3.RELEASE
    • 5.1.4.RELEASE
    • 5.1.5.RELEASE
    • 5.1.6.RELEASE
    • 5.1.7.RELEASE
    • 5.1.8.RELEASE
    • 5.1.9.RELEASE
    • 5.1.10.RELEASE
    • 5.1.11.RELEASE
    • 5.1.12.RELEASE
    • 5.1.13.RELEASE
    • 5.1.14.RELEASE
    • 5.1.15.RELEASE
    • 5.1.16.RELEASE
    • 5.1.17.RELEASE
    • 5.1.18.RELEASE
    • 5.1.19.RELEASE
    • 5.1.20.RELEASE
    • 5.2.0.RELEASE
    • 5.2.1.RELEASE
    • 5.2.2.RELEASE
    • 5.2.3.RELEASE
    • 5.2.4.RELEASE
    • 5.2.5.RELEASE
    • 5.2.6.RELEASE
    • 5.2.7.RELEASE
    • 5.2.8.RELEASE
    • 5.2.9.RELEASE
    • 5.2.10.RELEASE
    • 5.2.11.RELEASE
    • 5.2.12.RELEASE
    • 5.2.13.RELEASE
    • 5.2.14.RELEASE
    • 5.2.15.RELEASE
    • 5.2.16.RELEASE
    • 5.2.17.RELEASE
    • 5.2.18.RELEASE
    • 5.2.19.RELEASE
    • 5.2.20.RELEASE
    • 5.2.21.RELEASE
    • 5.2.22.RELEASE
    • 5.2.23.RELEASE
    • 5.2.24.RELEASE
    • 5.2.25.RELEASE
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.3.3
    • 5.3.4
    • javax.servlet-api:4.x
    • 5.3.5
    • 5.3.6
    • 5.3.7
    • 5.3.8
    • 5.3.9
    • 5.3.10
    • 5.3.11
    • 5.3.12
    • 5.3.13
    • 5.3.14
    • 5.3.15
    • 5.3.16
    • 5.3.17
    • 5.3.18
    • 5.3.19
    • 5.3.20
    • 5.3.21
    • 5.3.22
    • 5.3.23
    • 5.3.24
    • 5.3.25
    • 5.3.26
    • 5.3.27
    • 5.3.28
    • 5.3.29
    • 5.3.30
    • 5.3.31
    • 5.3.32
    • 5.3.33
    • 5.3.34
    • 5.3.35
    • 5.3.36
    • 5.3.37
    • 5.3.38
    • 5.3.39
    • javax.* -> jakarta.*
      jakarta.servlet-api:6.x
      Java 17 min
    • 6.0.0
    • 6.0.1
    • 6.0.2
    • 6.0.3
    • 6.0.4
    • 6.0.5
    • 6.0.6
    • 6.0.7
    • 6.0.8
    • 6.0.9
    • 6.0.10
    • 6.0.11
    • 6.0.12
    • 6.0.13
    • 6.0.14
    • 6.0.15
    • 6.0.16
    • 6.0.17
    • 6.0.18
    • 6.0.19
    • 6.0.20
    • 6.0.21
    • 6.0.22
    • 6.0.23
    • 6.1.0
    • 6.1.1
    • 6.1.2
    • 6.1.3
    • 6.1.4
    • 6.1.5
    • 6.1.6
    • 6.1.7
    • 6.1.8
    • 6.1.9
    • 6.1.10
    • 6.1.11
    • 6.1.12
    • 6.1.13
    • 6.1.14
    • 6.1.15
    • 6.1.16
    • 6.1.17
    • 6.1.18
    • 6.1.19
    • 6.1.20
    • 6.1.21
    • 6.2.0
    • 6.2.1
    • 6.2.2
    • 6.2.3
    • 6.2.4
    • 6.2.5
    • 6.2.6
    • 6.2.7
    • 6.2.8
    • 6.2.9
    • 6.2.10
    • 6.2.11
    • 6.2.12
    • 6.2.13
    • 6.2.14
    • 6.2.15
    • 6.2.16
    • 6.2.17
    • 6.2.18
    • 6.2.19
    • 7.0.0
    • 7.0.1
    • 7.0.2
    • 7.0.3
    • 7.0.4
    • 7.0.5
    • 7.0.6

    Versions in green have been tested.

  • junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
  • spring-test 7.0.6 (Spring TestContext Framework)
  • hamcrest-library 1.3 (Hamcrest library of matcher implementations)
  • jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
  • poi 5.5.1 (Apache POI - Java API To Access Microsoft Format Files)
  • JDK 25
  • Maven 3.9.11

Spring Excel View Example Select All Download
  • spring-excel-view-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • ForexExcelView.java
          • webapp
            • WEB-INF
              • views
        • test
          • java
            • com
              • logicbig
                • example

    See Also

    Join