Spring Boot: Excel, Csv and Pdf view example

Nowadays, exporting data into different format (Csv, Excel, Pdf ...) is a very general requirement in the most of any project. In this article, we demonstrate how to create an Excel, PDF and CSV views using Spring Boot. When configured properly, a Spring’s view resolver can generate the requested document from model data and send it to the client for downloading. the complete code can be found here.

Spring MVC View Configuration

First thing, We create a WebConfig class, Annotated with @Configuration to mark this class as a configuration file. I'm using contentNegotiatingViewResolver, which tells web controllers to return ModelAndViews or view names and based on various criteria, choose the right data representation strategy.

The highest priority hereby has the file extension which is used if available in the request. Next, the ViewResolver will look for a (definable) request parameter that identifies the view. If that does not help, the ViewResolver uses the Java Activation Framework to determine the Content-Type. If all fails, use the the HTTP Accept header. Of course the steps can be individually disabled. Check out this great article for more details.

In our example, we will be using the URL extension to help determine the media types. Also, we have set the default media type to TEXT_JSON in absence of file extension or when the filetype is unknown.

Also, We need to set the ContentNegotiationManager which will be injected by Spring, and different resolvers for each possible output format our application might produce.

Finally, we have created different view resolvers for PDF, XLS and CSV output which we will discuss next.

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    configurer
            .defaultContentType(MediaType.APPLICATION_JSON)
            .favorPathExtension(true);
}

/*
 * Configure ContentNegotiatingViewResolver
 */
@Bean
public ViewResolver contentNegotiatingViewResolver(ContentNegotiationManager manager) {
    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    resolver.setContentNegotiationManager(manager);

    // Define all possible view resolvers
    List<ViewResolver> resolvers = new ArrayList<>();

    resolvers.add(csvViewResolver());
    resolvers.add(excelViewResolver());
    resolvers.add(pdfViewResolver());

    resolver.setViewResolvers(resolvers);
    return resolver;
}

/*
 * Configure View resolver to provide XLS output using Apache POI library to
 * generate XLS output for an object content
 */
@Bean
public ViewResolver excelViewResolver() {
    return new ExcelViewResolver();
}

/*
 * Configure View resolver to provide Csv output using Super Csv library to
 * generate Csv output for an object content
 */
@Bean
public ViewResolver csvViewResolver() {
    return new CsvViewResolver();
}

/*
 * Configure View resolver to provide Pdf output using iText library to
 * generate pdf output for an object content
 */
@Bean
public ViewResolver pdfViewResolver() {
    return new PdfViewResolver();
}
}

Creating Controller

Nothing much to say here, The Export controller adds some data to the Model which we’ll display on the views.

@Controller
public class Export {

@Autowired
UserService userService;

/**
 * Handle request to download an Excel document
 */
@RequestMapping(value = "/download", method = RequestMethod.GET)
public String download(Model model) {
    model.addAttribute("users", userService.findAllUsers());
    return "";
}
}

Excel View

There are 2 file formats in which we can create an Excel document. The .xls is the old format, the .xlsx is the new format which is XML based. We are using apache POI to create excel files, when creating .xls documents make sure the org.apache.poi:poi dependency is on the classpath. When working with .xlsx files, you need the org.apache.poi:poi-ooxml dependency.

ExcelView which extends from AbstractXlsView. We create the excel document by overriding the buildExcelDocument, the rest is self explanatory.

public class ExcelView extends AbstractXlsView{

@Override
protected void buildExcelDocument(Map<String, Object> model,
                                  Workbook workbook,
                                  HttpServletRequest request,
                                  HttpServletResponse response) throws Exception {

    // change the file name
    response.setHeader("Content-Disposition", "attachment; filename=\"my-xls-file.xls\"");

    @SuppressWarnings("unchecked")
    List<User> users = (List<User>) model.get("users");

    // create excel xls sheet
    Sheet sheet = workbook.createSheet("User Detail");
    sheet.setDefaultColumnWidth(30);

    // create style for header cells
    CellStyle style = workbook.createCellStyle();
    Font font = workbook.createFont();
    font.setFontName("Arial");
    style.setFillForegroundColor(HSSFColor.BLUE.index);
    style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
    font.setBold(true);
    font.setColor(HSSFColor.WHITE.index);
    style.setFont(font);


    // create header row
    Row header = sheet.createRow(0);
    header.createCell(0).setCellValue("Firstname");
    header.getCell(0).setCellStyle(style);
    header.createCell(1).setCellValue("LastName");
    header.getCell(1).setCellStyle(style);
    header.createCell(2).setCellValue("Age");
    header.getCell(2).setCellStyle(style);
    header.createCell(3).setCellValue("Job Title");
    header.getCell(3).setCellStyle(style);
    header.createCell(4).setCellValue("Company");
    header.getCell(4).setCellStyle(style);
    header.createCell(5).setCellValue("Address");
    header.getCell(5).setCellStyle(style);
    header.createCell(6).setCellValue("City");
    header.getCell(6).setCellStyle(style);
    header.createCell(7).setCellValue("Country");
    header.getCell(7).setCellStyle(style);
    header.createCell(8).setCellValue("Phone Number");
    header.getCell(8).setCellStyle(style);



    int rowCount = 1;

    for(User user : users){
        Row userRow =  sheet.createRow(rowCount++);
        userRow.createCell(0).setCellValue(user.getFirstName());
        userRow.createCell(1).setCellValue(user.getLastName());
        userRow.createCell(2).setCellValue(user.getAge());
        userRow.createCell(3).setCellValue(user.getJobTitle());
        userRow.createCell(4).setCellValue(user.getCompany());
        userRow.createCell(5).setCellValue(user.getAddress());
        userRow.createCell(6).setCellValue(user.getCity());
        userRow.createCell(7).setCellValue(user.getCountry());
        userRow.createCell(8).setCellValue(user.getPhoneNumber());

        }

}

}

Spring also provides 2 other abstract classes AbstractXlsxView and AbstractXlsxStreamingView to create xlsx files. When working with large excel documents it is profitable to use the streaming xlsx view. The streaming view uses less memory and can improve performance of large excel documents.

PDF view

For that we'll use iText library. Spring provides an AbstractPdfView abstract class which can be subclassed to create a helper class for generating PDF documents. However, it has a big drawback which the AbstractPdfView class only supports old API version of iText i.e. it is using the package com.lowagie.* (iText version <= 2.1.7) while the recent iText’s package changes to com.itextpdf.* (iText version >= 5.x)

The old iText version is no longer available nor supported, so subclassing AbstractPdfView class is discouraged. Instead, I recommend to subclass the AbstractView class to create an iText 5.x-compatible version.

public abstract class AbstractPdfView extends AbstractView {

public AbstractPdfView() {
    setContentType("application/pdf");
}

@Override
protected boolean generatesDownloadContent() {
    return true;
}

@Override
protected final void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception  {

    // IE workaround: write into byte array first.
    ByteArrayOutputStream baos = createTemporaryOutputStream();

    // Apply preferences and build metadata.
    Document document = new Document(PageSize.A4.rotate(), 36, 36, 54, 36);
    PdfWriter writer = PdfWriter.getInstance(document, baos);
    prepareWriter(model, writer, request);
    buildPdfMetadata(model, document, request);

    // Build PDF document.
    document.open();
    buildPdfDocument(model, document, writer, request, response);
    document.close();

    // Flush to HTTP response.
    response.setHeader("Content-Disposition", "attachment");    // make browser to ask for download/display
    writeToResponse(response, baos);
}

protected void prepareWriter(Map<String, Object> model, PdfWriter writer, HttpServletRequest request) throws DocumentException {
    writer.setViewerPreferences(getViewerPreferences());
}

protected int getViewerPreferences() {
    return PdfWriter.ALLOW_PRINTING | PdfWriter.PageLayoutSinglePage;
}


protected void buildPdfMetadata(Map<String, Object> model, Document document, HttpServletRequest request) {
}


protected abstract void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer,
                                         HttpServletRequest request, HttpServletResponse response) throws Exception;
}

After that, all what we need to do next is create PdfView class and extend from the previously created AbstractPdfView and override the buildPdfDocument(..) method to create our PDF document.

CSV View

For that, we will follow the exact same approach used for PDF generation, which involves creating an abstract view class, a concrete view class and view resolver. I'm using Super CSV to generate csv files.

So, below the code for AbstractCsvView that Subclass the Spring’s AbstractView class:

public abstract class AbstractCsvView extends AbstractView {

private static final String CONTENT_TYPE = "text/csv";


private String url;


public AbstractCsvView() {
    setContentType(CONTENT_TYPE);
}


public void setUrl(String url) {
    this.url = url;
}

@Override
protected boolean generatesDownloadContent() {
    return true;
}


@Override
protected final void renderMergedOutputModel(
        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    response.setContentType(getContentType());
    buildCsvDocument(model, request, response);
}



protected abstract void buildCsvDocument(
        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response)
        throws Exception;


}

Then, we write an implementation of the AbstractCsvView class and make it implements the buildCsvDocument() method as follows:

public class CsvView extends AbstractCsvView {
@Override
protected void buildCsvDocument(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

    response.setHeader("Content-Disposition", "attachment; filename=\"my-csv-file.csv\"");

    List<User> users = (List<User>) model.get("users");
    String[] header = {"Firstname","LastName","LastName","JobTitle","Company","Address","City","Country", "PhoneNumber"};
    ICsvBeanWriter csvWriter = new CsvBeanWriter(response.getWriter(),
            CsvPreference.STANDARD_PREFERENCE);

    csvWriter.writeHeader(header);

    for(User user : users){
        csvWriter.write(user, header);
    }
    csvWriter.close();


}
}

Demo

Run the app, and to download:

  • pdf file use: localhost:8080/download.pdf
  • XLS file use: localhost:8080/download.xls
  • CSV file use: localhost:8080/download.csv