Return generated pdf using spring MVC
Asked Answered
T

2

84

I am using Spring MVC .I have to write a service that would take input from the request body, add the data to the pdf and returns the pdf file to the browser. The pdf document is generated using itextpdf. How can I do this using Spring MVC. I have tried using this

@RequestMapping(value="/getpdf", method=RequestMethod.POST)
public Document getPDF(HttpServletRequest request , HttpServletResponse response, 
      @RequestBody String json) throws Exception {
    response.setContentType("application/pdf");
    response.setHeader("Content-Disposition", "attachment:filename=report.pdf");
    OutputStream out = response.getOutputStream();
    Document doc = PdfUtil.showHelp(emp);
    return doc;
}

showhelp function that generates the pdf. I am just putting some random data in the pdf for time being.

public static Document showHelp(Employee emp) throws Exception {
    Document document = new Document();

    PdfWriter.getInstance(document, new FileOutputStream("C:/tmp/report.pdf"));
    document.open();
    document.add(new Paragraph("table"));
    document.add(new Paragraph(new Date().toString()));
    PdfPTable table=new PdfPTable(2);

    PdfPCell cell = new PdfPCell (new Paragraph ("table"));

    cell.setColspan (2);
    cell.setHorizontalAlignment (Element.ALIGN_CENTER);
    cell.setPadding (10.0f);
    cell.setBackgroundColor (new BaseColor (140, 221, 8));                                  

    table.addCell(cell);                                    
    ArrayList<String[]> row=new ArrayList<String[]>();
    String[] data=new String[2];
    data[0]="1";
    data[1]="2";
    String[] data1=new String[2];
    data1[0]="3";
    data1[1]="4";
    row.add(data);
    row.add(data1);

    for(int i=0;i<row.size();i++) {
      String[] cols=row.get(i);
      for(int j=0;j<cols.length;j++){
        table.addCell(cols[j]);
      }
    }

    document.add(table);
    document.close();

    return document;   
}

I am sure this is wrong. I want that pdf to be generated and save/open dialog box to be opened through the browser, so that it can be stored in the client's file system. Please help me out.

Tseng answered 20/5, 2013 at 15:20 Comment(0)
F
161

You were on the right track with response.getOutputStream(), but you're not using its output anywhere in your code. Essentially what you need to do is to stream the PDF file's bytes directly to the output stream and flush the response. In Spring you can do it like this:

@RequestMapping(value="/getpdf", method=RequestMethod.POST)
public ResponseEntity<byte[]> getPDF(@RequestBody String json) {
    // convert JSON to Employee 
    Employee emp = convertSomehow(json);

    // generate the file
    PdfUtil.showHelp(emp);

    // retrieve contents of "C:/tmp/report.pdf" that were written in showHelp
    byte[] contents = (...);

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_PDF);
    // Here you have to set the actual filename of your pdf
    String filename = "output.pdf";
    headers.setContentDispositionFormData(filename, filename);
    headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
    ResponseEntity<byte[]> response = new ResponseEntity<>(contents, headers, HttpStatus.OK);
    return response;
}

Notes:

  • use meaningful names for your methods: naming a method that writes a PDF document showHelp is not a good idea
  • reading a file into a byte[]: example here
  • I'd suggest adding a random string to the temporary PDF file name inside showHelp() to avoid overwriting the file if two users send a request at the same time
Firm answered 20/5, 2013 at 18:54 Comment(11)
That helped me. Thanks a lot!!! I have one more. here I am storing the generated pdf to my file system and then reading it and converting it to bytes. What should I do if I have to convert the generated pdf on the go. Thanks in advanceTseng
Instead of using FileOutputStream in your PdfWriter.getInstance() you could use a ByteArrayOutputStream.Firm
@kryger, do you know how to open the PDF file in a browser window? I used your method and I prompts the user to save the file immediately :)Fascicle
Why store a file on the filesystem?Strata
@Strata not sure what you mean (the file example comes from OP's question, in the comments I mentioned how to do it without FileOutputStream), but "thanks" for the downvote.Firm
@kryger: My bad, you are right. Sorry for the downvote, cant undo it now (past 2 hours). I've upvoted you elsewhere to compensate.Strata
I am not understanding the root cause of the issue . I have a simple endpoint from where I want to render pdf. I am able to download the pdf but it is blank and if I use "application/octet-stream" I see that contents are there. So what might be the issue ? ThanksPotage
Thank you, it worked fine. I've used GET instead of POST: @RequestMapping(value="/getpdf/{idpdf}", method=RequestMethod.GET)Legislator
Since Spring 4.3 MediaType.APPLICATION_PDF is available as static variable.Floccule
For me doing this in Chrome and attempting to save it results in a Failed - network error. Works fine on other browsers and can be worked around with print -> save as pdf. Why does this only happen in chrome and how can I correct it?Telson
@Firm how to call this REST API from angular code and show pdf on browser?Confounded
D
1

Generally, you do not want to use ResponseEntity<byte[]>, unless you are certain that the binary data is very small. Typically you will want to use ResponseEntity<InputStreamResource>

@GetMapping("/file")
public ResponseEntity<InputStreamResource> getFile(){
  // figure out what filePath is. filePath=...
  try (FileInputStream fileInputStream = new FileInputStream(filePath)){
    InputStreamResource inputStreamResource = new InputStreamResource(fileInputStream);
    HttpHeaders headers = new HttpHeaders();
    headers.setContentLength(Files.size(Paths.get(filePath)));//optional
    headers.setContentDisposition("attachment", "somefile.pdf"); //optional
    headers.setContentType("application/pdf"); //optional
    return new ResponseEntity(inputStreamResource, headers, HttpStatus.OK);
}
Dulosis answered 16/9, 2023 at 0:57 Comment(1)
Be aware that this will throw a 'Stream Closed' exception as the try-with-resource block will call close() on the stream before the response is returned from the controller. You probably want to use a standard try block here instead.Giraffe

© 2022 - 2024 — McMap. All rights reserved.