Search code examples
javaspring-mvcexceptioniooutputstream

how to deal with exceptions on a Spring controller while generating a file


I've implemented a Spring controller that generates a PDF file, consuming my service layer. The problem is that when some exception is thrown during the execution of the method, even if I get the exception with a try-catch block, spring tries to resolve the view based on the current URL.

I honestly have no idea on what is causing this behavior.

    @RequestMapping("/cliente")
    public void relatorioCliente(EdicaoMovimentacaoWrapper wrapper, @AuthenticationPrincipal Usuario usuario) {
        try {
            gerarReportService.relatorioParaCliente(wrapper.getContaId(), usuario);

        } catch (Exception e) {
            e.printStackTrace();
            // TODO alguma coisa
        }
    }

the relatorioParaCliente method generates the PDF and exports it to the response's OutputStream.

Expected: the exception gets caught, the stack trace gets printed and nothing happens to the user. Actual result: Spring redirects the user to views/cliente.jsp

UPDATE

I've tried changing the return type of the method so it looks like this now:

    @RequestMapping("/cliente")
    public ModelAndView relatorioCliente(EdicaoCadastroMovimentacaoWrapper wrapper, @AuthenticationPrincipal Usuario usuario) {
        try {
            gerarReportService.relatorioParaCliente(wrapper.getContaId(), usuario);

        } catch (Exception e) {
            e.printStackTrace();
            return new ModelAndView("/contas/" + wrapper.getContaId());
        }
        return null;
    }

But this has no effect on my code. I suppose that this does not affect the code because the outputStream gets used on the service. take a look:

@Service
public class ExportarReportPdfService {

    @Autowired
    private HttpServletResponse res;

    public void exportar(List<JasperPrint> jasperPrintList) throws IOException {

        JRPdfExporter exporter = new JRPdfExporter();

        exporter.setExporterInput(SimpleExporterInput.getInstance(jasperPrintList));

        exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(res.getOutputStream()));

        SimplePdfExporterConfiguration configuration = new SimplePdfExporterConfiguration();

        configuration.setCreatingBatchModeBookmarks(true);

        exporter.setConfiguration(configuration);

        res.setContentType("application/x-pdf");
        res.setHeader("Content-disposition", "inline; filename=relatorio.pdf" );


        try {
            exporter.exportReport();
        } catch (Exception e) {
            e.printStackTrace();
            throw new CriacaoReportException("Erro ao exportar para PDF");
        } finally {
            OutputStream out = res.getOutputStream();
            out.flush();
            out.close();

        }
    }


Solution

  • Here is what I did to solve the problem:

    instead of Autowiring the response on the service and exporting the pdf from there, I generate a byteArray and return it to the controller:

    @Service
    public class ExportarReportPdfService {
    
        public byte[] exportar(List<JasperPrint> jasperPrintList)  {
    
            final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    
            JRPdfExporter exporter = new JRPdfExporter();
    
            exporter.setExporterInput(SimpleExporterInput.getInstance(jasperPrintList));
    
            exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(outputStream));
    
            [OMITED CODE]
    
            try {
                exporter.exportReport();
                return outputStream.toByteArray();
            } catch (Exception e) {
                e.printStackTrace();
                throw new CriacaoReportException("Erro ao exportar para PDF");
            }
        }
    }
    

    And the controller sends the response as a ResponseEntity :

            @RequestMapping("/geral")
            public ResponseEntity<InputStreamResource> relatorioAdministrador(EdicaoCadastroMovimentacaoWrapper wrapper, @AuthenticationPrincipal Usuario usuario) {
    
                byte[] arquivo = gerarReportService.relatorioParaAdmin(wrapper.getContaId(), usuario);
    
                return ResponseEntity
                        .ok()
                        .contentLength(arquivo.length)
                        .contentType(MediaType.parseMediaType("application/pdf"))
                        .header("Content-Disposition", "attachment; filename=relatorio.pdf")
                        .body(new InputStreamResource(new ByteArrayInputStream(arquivo)));
            }
    
    
    

    So if any exception occurs it will be caught in the ExceptionHandler as @cmoetzing explained in the comments:

        @ExceptionHandler(CriacaoReportException.class)
        public ModelAndView trataGeracaoRelatorio(CriacaoReportException exception) {
            return new ModelAndView("redirect:/");
        }