29

Let say I have CSV data in a string and want to return it from a Spring controller. Imagine the data looks like this

a,b,c 
1,2,3
4,5,6

No matter what I have tried, the newlines come out as literally '\n' in the response content, and if I double escape them as in "\n", the response just contains the double backslashes too. In general, how to I return plain text data with newlines in it without the newlines being modified? I know how to return plain text, but still, the content comes with escaped newlines... This is what I current have (using Spring 3.0.5, not by choice)

@RequestMapping(value = "/api/foo.csv")
public ResponseEntity<String> fooAsCSV() {

    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.add("Content-Type", "text/plain; charset=utf-8");

    String data = "a,b,c\n1,2,3\n3,4,5";
    return new ResponseEntity<>(data, responseHeaders, HttpStatus.OK);
}

Which produces literally the string

"a,b,c\n1,2,3\n,3,4,5"

In the browser. How do I make it produce the correct data with new lines in tact as shown above?

6
  • What if your method signature says public String fooAsCSV() with @RequestMapping(value = "/api/foo.csv", method = RequestMethod.GET, produces="text/plain") and then return "a,b,c\n1,2,3\n,3,4,5"; i.e. returning a string directly instead of using ResponseEntity Commented Apr 8, 2014 at 20:54
  • Unfortunately that is only available in Spring 3.1+ Commented Apr 8, 2014 at 20:59
  • Ouch, missed the "Spring 3.0.5" bit, sorry. Commented Apr 8, 2014 at 21:01
  • Hey no worries, thanks for reading and taking the time. Still not sure how to do this, I found a slightly hacky way which is to set the content type to text/html and put <br/> instead of \n. That makes me feel dirty. Commented Apr 8, 2014 at 21:15
  • Maybe you can try to write a trivial message converter yourself. I'm not sure where the problem lies but I bet you could try and steal the StringHttpMessageConverter from another version of Spring and see what happens. I tested your method in Spring 4 and it works like expected. Commented Apr 8, 2014 at 21:36

5 Answers 5

34

You could write to the response directly using e.g.

@RequestMapping(value = "/api/foo.csv")
public void fooAsCSV(HttpServletResponse response) {         
    response.setContentType("text/plain; charset=utf-8");
    response.getWriter().print("a,b,c\n1,2,3\n3,4,5");
}

Since the return type is void and HttpServletResponse is declared as a method argument the request is assumed to be completed when this method returns.

Sign up to request clarification or add additional context in comments.

Comments

9

You can use the library supercsv.

<dependency>
  <groupId>net.sf.supercsv</groupId>
  <artifactId>super-csv</artifactId>
  <version>2.1.0</version>
</dependency>

Here is how to use it:

1- define your model class that you want to write as csv:

public class Book {
private String title;
private String description;
private String author;
private String publisher;
private String isbn;
private String publishedDate;
private float price;

public Book() {
}

public Book(String title, String description, String author, String publisher,
        String isbn, String publishedDate, float price) {
    this.title = title;
    this.description = description;
    this.author = author;
    this.publisher = publisher;
    this.isbn = isbn;
    this.publishedDate = publishedDate;
    this.price = price;
}

// getters and setters...
}

2- Do the following magic:

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import javax.servlet.http.HttpServletResponse;


import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.supercsv.io.CsvBeanWriter;
import org.supercsv.io.ICsvBeanWriter;
import org.supercsv.prefs.CsvPreference;

/**
 * This Spring controller class implements a CSV file download functionality.
 *
 */
@Controller
public class CSVFileDownloadController {
    @RequestMapping(value = "/downloadCSV")
    public void downloadCSV(HttpServletResponse response) throws IOException {

        String csvFileName = "books.csv";

        response.setContentType("text/csv");

        // creates mock data
        String headerKey = "Content-Disposition";
        String headerValue = String.format("attachment; filename=\"%s\"",
            csvFileName);
        response.setHeader(headerKey, headerValue);

        Book book1 = new Book("Effective Java", "Java Best Practices",
            "Joshua Bloch", "Addision-Wesley", "0321356683", "05/08/2008",
            38);

        Book book2 = new Book("Head First Java", "Java for Beginners",
            "Kathy Sierra & Bert Bates", "O'Reilly Media", "0321356683",
            "02/09/2005", 30);

        Book book3 = new Book("Thinking in Java", "Java Core In-depth",
            "Bruce Eckel", "Prentice Hall", "0131872486", "02/26/2006", 45);

        Book book4 = new Book("Java Generics and Collections",
            "Comprehensive guide to generics and collections",
            "Naftalin & Philip Wadler", "O'Reilly Media", "0596527756",
            "10/24/2006", 27);

        List<Book> listBooks = Arrays.asList(book1, book2, book3, book4);

        // uses the Super CSV API to generate CSV data from the model data
        ICsvBeanWriter csvWriter = new CsvBeanWriter(response.getWriter(),
            CsvPreference.STANDARD_PREFERENCE);

        String[] header = { "Title", "Description", "Author", "Publisher",
            "isbn", "PublishedDate", "Price" };

        csvWriter.writeHeader(header);

        for (Book aBook : listBooks) {
            csvWriter.write(aBook, header);
        }

        csvWriter.close();
    }
}

Comments

8

Have you tried @ResponseBody on your controller method?

@RequestMapping(value = "/api/foo.csv")
@ResponseBody
public String fooAsCSV(HttpServletResponse response) {         
    response.setContentType("text/plain; charset=utf-8");
    String data = "a,b,c\n1,2,3\n3,4,5";
    return data;
}

Edit: Spring docs explain it here: http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-responsebody

2 Comments

I did add the @ResponseBody annotation, but hey, writing to the output stream directly might do the trick. Let me try that.
copy and paste error - return type now String. See docs.spring.io/spring/docs/3.0.x/spring-framework-reference/…
4

here is a more detailed example for Bart's answer:

@GetMapping(value = "csv")
public void exportCsv(HttpServletResponse response) {
    try {
        // prepare response encoding and Headers
        response.setContentType("text/csv");
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setHeader("Content-Disposition", "attachment; filename=a.csv"); // specify the real file name users will get when download
        try (Writer writer = new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8)) {
            // writer.write('\uFEFF'); // BOM is essential in some scenary
            writer.write("key,value\n");
            List<Row> row = rowService.getAll();
            for (Column column : row) {
                writer.write(String.format("%d,%s)\n",column.getKey(), column.getValue()));
            }
            writer.flush(); // DONNOT forget to flush writer after everything done
        } // writer will be closed automatically
    } catch (IOException e) {
        log.error("failed to export csv", e);
    }
}

Comments

3

I had a similar task lately. Used ResponseEntity<byte[]> and the response header Content-Disposition. Like this:

@RequestMapping(value = "/api/foo.csv")
public ResponseEntity<byte[]> fooAsCSV() {

  HttpHeaders responseHeaders = new HttpHeaders();
  responseHeaders.add("Content-Type", "application/vnd.ms-excel");
  responseHeaders.add("Content-Disposition", "attachment; filename=abc.csv");

  String data = "a,b,c\n1,2,3\n3,4,5";
  return new ResponseEntity<>(data.getBytes("ISO8859-15"), responseHeaders, HttpStatus.OK);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.