Issue
Reproduction of the error here. Please remove the comments before the exception throwing to see the problem. Path is: http://localhost:8080/api/download
Original Question: I am allowing users to download reports using JasperReports in an Angular7 App, but the problem is only connected to Spring Boot. The report generation works, but I can't get the file download to behave as I expected.
Currently:
The download link is used with an href
, link with target="_blank"
. The user clicks on it and the browser opens a new tab (in the background) and pops-up the File Save As
window. If everything is okay, the file is saved without a problem. However if there is an exception during the PDF generation somewhere, the browser still pops-up the File Save As
window and allows the user to save the file, it will complete, but the file will be 0 bytes
.
Should be: When there is an exception, the browser should open a new tab with an error message of some sort, but If If there were no errors, it should display the File Save As window.
Code:
@GetMapping("/salary-report/{id}")
public void generateSalaryReport(@PathVariable("id") long salaryReportId, HttpServletResponse response) throws IOException, JRException, SQLException {
JasperPrint jasperPrint;
var salaryReport = salaryReportRepositoryEx.findById(salaryReportId).orElseThrow(ResourceNotFoundException::new);
try (OutputStream out = response.getOutputStream()) {
HashMap<String, Object> parameters = new HashMap<>();
parameters.put("ReportId", salaryReport.getId());
// Set meta data
response.setContentType("application/x-download");
response.setHeader(
"Content-Disposition",
String.format("attachment; filename=\"%s%s-report%s-%s-%s.pdf\"",
.... parameters
)
);
// Set report
jasperPrint = salaryReportJasperReport.render(parameters); // exception usually here
JasperExportManager.exportReportToPdfStream(jasperPrint, out);
} catch (Exception e) {
// I tried changing the content type on Exception, but the same
response.setContentType("text/plain");
response.setHeader("Content-Disposition", null);
throw e;
}
}
HTML Code with the link (Angular7):
<td>
<a [href]="serverApiUrl+'/jasper/salary-report/'+salaryReport.id"
target="_blank"
>
PDF Download
</a>
</td>
Edit: If I just manually navigate to a full download URL, same thing happens.
Edit2: Tried it in postman too. Using an invalid report id returns the expected 404 Json (ResourceNotFoundException
), but moving forward with the code always returns 200 OK (even when I manually set the HTTP Code to 500 in the catch block) and the body is empty.
Edit3: I tried using an exception handler:
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public class JasperReportGenerationFailedException extends Exception {
public JasperReportGenerationFailedException() {
super();
}
public JasperReportGenerationFailedException(String message) {
super(message);
}
public JasperReportGenerationFailedException(String message, Throwable cause) {
super(message, cause);
}
public JasperReportGenerationFailedException(Throwable cause) {
super(cause);
}
protected JasperReportGenerationFailedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
@ExceptionHandler(JasperReportGenerationFailedException.class )
public ResponseEntity<String> handleJasperException(JasperReportGenerationFailedException ex) {
log.error("Salary Report generation failed.", ex);
return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
@GetMapping("/salary-report/{id}")
public void generateSalaryReport(@PathVariable("id") long salaryReportId, HttpServletResponse response) throws JasperReportGenerationFailedException {
JasperPrint jasperPrint;
var salaryReport = salaryReportRepositoryEx.findById(salaryReportId).orElseThrow(ResourceNotFoundException::new);
try (OutputStream out = response.getOutputStream()) {
HashMap<String, Object> parameters = new HashMap<>();
parameters.put("ReportId", salaryReport.getId());
// Set meta data
response.setContentType("application/x-download");
response.setHeader(
"Content-Disposition",
...
)
);
if (1 == 1/1) { // for testing, i always throw
throw new Exception("Test");
}
// Set report
jasperPrint = salaryReportJasperReport.render(parameters);
JasperExportManager.exportReportToPdfStream(jasperPrint, out);
} catch (Exception e) {
throw new JasperReportGenerationFailedException(e);
}
}
Solution
This issue is not solvable completely. The HttpServletResponse
does not allow the removal of any header after setting it, so I had to move the rendering before the response changes (before response.setHeader("Content-Disposition",..
). This allows the exception to be thrown normally.
More info here.
Answered By - szab.kel