JasperReports is a library entirely written in Java and it is able to use data coming from any kind of data source and produce pixel-perfect documents that can be viewed, printed or exported in a variety of document formats including HTML, PDF, Excel, OpenOffice and Word.

Introduction

We will look at two types of HTML emails that can be sent with Spring’s abstraction of JavaMail:

  1. HTML email with no inline
  2. HTML email with inline (using image resources)

Generally when sending emails as HTML with Spring, you can use Thymeleaf, Freemaker etc to template your HTML. In this post we will look at how to achieve the same thing using JasperReports.

What we will not be doing in this post is how to send attachments with Spring’s abstraction of JavaMail.

Prerequisites

Project Structure

At the end of this guide our folder structure will look similar to the following:

.
|__src/
|  |__main/
|  |  |__java/
|  |  |  |__com/
|  |  |  |  |__juliuskrah/
|  |  |  |  |  |__jasper/
|  |  |  |  |  |  |__Application.java
|  |  |  |  |  |  |__ApplicationProperties.java
|  |  |  |  |  |  |__mail/
|  |  |  |  |  |  |  |__EmailService.java
|  |  |  |  |  |  |  |__HtmlEmailService.java
|  |  |  |  |  |  |  |__JasperReportsService.java
|  |  |  |  |  |  |  |__ReportService.java
|  |  |  |  |  |  |__storage/
|  |  |  |  |  |  |  |__FileSystemStorageService.java
|  |  |  |  |  |  |  |__StorageException.java
|  |  |  |  |  |  |  |__StorageFileNotFoundException.java
|  |  |  |  |  |  |  |__StorageService.java
|  |  |__resources/
|  |  |  |__reports/
|  |  |  |  |__html_inline.jrxml
|  |  |  |  |__html.jrxml
|  |  |  |__application.yaml
|  |  |  |__cherry.png
|  |  |  |__logo.png
|__pom.xml

How to complete this guide

To complete this guide, download (zip| tar.gz) and unzip the source repository for this guide. You also need to set the following environment variables:

  1. MAIL_HOST
  2. MAIL_PASSWORD
  3. MAIL_PORT
  4. MAIL_USERNAME

HTML Mail without Inline

Download and extract the base project if you haven’t already done so. Create the class HtmlEmailService that implements the EmailService. We will implement sendHtmlEmail() for the time being:

file: src/main/java/com/juliuskrah/jasper/mail/HtmlEmailService.java

@Async
@Component
public class HtmlEmailService implements EmailService {
  private final JavaMailSender javaMail;
  private final ApplicationProperties properties;

  public HtmlEmailService(JavaMailSender javaMail, ApplicationProperties properties) {
    this.javaMail = javaMail;
    this.properties = properties;
  }

  @Override
  public void sendHtmlEmail(String recipient, String html) {
    final MimeMessage message = javaMail.createMimeMessage();
    try {
      final MimeMessageHelper helper = new MimeMessageHelper(message, "UTF-8");
      helper.setFrom(properties.getMail().getSender(),
        properties.getMail().getPersonal());
      helper.setTo(recipient);
      helper.setSubject(properties.getMail().getMessageSubject());
      // Set to true for HTML
      helper.setText(html, true);
      javaMail.send(message);
    } catch (MessagingException | UnsupportedEncodingException e) {
      e.printStackTrace();
    }
  }

  // Implementation details omitted for brevity
}

The @Async annotation tells the Spring Framework to run the methods of EmailService asynchrousnously when invoked. The html of sendHtmlEmail() parameter contains the HTML markup.

Next we need to implement the ReportService:

file: src/main/java/com/juliuskrah/jasper/mail/JasperReportsService.java

@Component
public class JasperReportsService implements ReportService {
  private final StorageService storageService;

  public JasperReportsService(StorageService storageService) {
    this.storageService = storageService;
  }

  @Override
  public String generateHtmlReport(String inputFileName, Map<String, Object> params) {
    return generateHtmlReport(inputFileName, params, new JREmptyDataSource());
  }

  @Override
  public String generateHtmlReport(String inputFileName, Map<String, Object> params,
    JRDataSource dataSource) {
    byte[] bytes = null;
    JasperReport jasperReport = null;
    try (ByteArrayOutputStream byteArray = new ByteArrayOutputStream()) {
      // Check if a compiled report exists
      if (storageService.jasperFileExists(inputFileName)) {
        jasperReport = (JasperReport) JRLoader
          .loadObject(storageService.loadJasperFile(inputFileName));
      }
      // Compile report from source and save
      else {
        String jrxml = storageService.loadJrxmlFile(inputFileName);
        jasperReport = JasperCompileManager.compileReport(jrxml);
        // Save compiled report. Compiled report is loaded next time
        JRSaver.saveObject(jasperReport,
          storageService.loadJasperFile(inputFileName));
      }
      JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, params, dataSource);
      Exporter<ExporterInput, HtmlReportConfiguration, HtmlExporterConfiguration, HtmlExporterOutput> exporter;
      // HTML exporter
      exporter = new HtmlExporter();
      // Set output to byte array
      exporter.setExporterOutput(new SimpleHtmlExporterOutput(byteArray));
      // Set input source
      exporter.setExporterInput(new SimpleExporterInput(jasperPrint));
      // Export to HTML
      exporter.exportReport();
      bytes = byteArray.toByteArray();
    }
    catch (JRException | IOException e) {
      e.printStackTrace();
    }
    return new String(bytes);
  }

  // Implementation details omitted for brevity
}

Let’s test sending a HTML mail. Be sure to set your SMTP configuration properties in application.yaml:

file: src/main/java/com/juliuskrah/jasper/Application.java

public class Application {
  private final ReportService reportService;
  private final EmailService emailService;
  private final ApplicationProperties properties;

  public Application(ReportService reportService, EmailService emailService, ApplicationProperties properties) {
    this.reportService = reportService;
    this.emailService = emailService;
    this.properties = properties;
  }
  // ...

  @Scheduled(cron = "${com.juliuskrah.cron}")
  void sendHTMLEmail() {
    Set<Recipient> recipients = properties.getMail().getRecipients();

    for (Recipient recipient : recipients) {
      Map<String, Object> params = new HashMap<>();
      params.put("username", recipient.getUsername());
      String html = reportService.generateHtmlReport("html", params);
      emailService.sendHtmlEmail(recipient.getEmail(), html);
    }
  }
}

The html String parameter is specified by ( src/main/resources/reports/html.jrxml ). Before you execute, do not forget to set the values of com.juliuskrah.cron and com.juliuskrah.mail.recipients[0].email in the configuration file. Be sure to set an appropriate cron expression.

HTML without inline

Jasper HTML Email

Not very elegant, but you get the general idea.

HTML Mail with Inline

Generating HTML in JasperReports with image resources is a bit tricky. JasperReports handles the image resources separately before associating them to the HTML. We will implement the generateInlineHtmlReport() to handle the image resources. The implementation of generateInlineHtmlReport() is similar to generateHtmlReport(). I will just highlight the code change. You can view the full implementation in the linked file below:

file: src/main/java/com/juliuskrah/jasper/mail/JasperReportsService.java

public class JasperReportsService implements ReportService {
  // ...
  @Override
  public List<Object> generateInlineHtmlReport(String inputFileName,
    Map<String, Object> params, JRDataSource jRDataSource) {
    // ...
    // This will be populated with the image name, and byte[] resource
    Map<String, byte[]> resourcesMap = new HashMap<>();
    SimpleHtmlExporterOutput htmlExporterOutput = new SimpleHtmlExporterOutput(byteArray);
    htmlExporterOutput.setImageHandler(new MapHtmlResourceHandler((resourcesMap)) {
      @Override
      public String getResourcePath(String id) {
        // Add the Content ID
        return "cid:" + id;
      }
    });

    exporter.setExporterOutput(htmlExporterOutput);
    exporter.setExporterInput(new SimpleExporterInput(jasperPrint));
    exporter.exportReport();
    String html = new String(byteArray.toByteArray());
    result.add(html);
    // Add the populated map
    result.add(resourcesMap);
    return result;
  }
}

Implement the sendHtmlEmail() overload that can handle inline resources:

file: src/main/java/com/juliuskrah/jasper/mail/HtmlEmailService.java

public class HtmlEmailService implements EmailService {
  // ...
  @Override
  public void sendHtmlEmail(String recipient, String html, Map<String, byte[]> imageSource) {
    // Set true for inline
    final MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
    // ...

    // Set to true for HTML
    helper.setText(html, true);
    for(Entry<String, byte[]> val : imageSource.entrySet()) {
      helper.addInline(val.getKey(), new ByteArrayResource(val.getValue()), "image/png");
    }
    javaMail.send(message);
  }
}

Test the inline email:

file: src/main/java/com/juliuskrah/jasper/Application.java

public class Application {
  // ...

  @Scheduled(cron = "${com.juliuskrah.inline-cron}")
  void sendInlineHTMLEmail() {
    Set<Recipient> recipients = properties.getMail().getRecipients();

    for (Recipient recipient : recipients) {
      Map<String, Object> params = new HashMap<>();
      params.put("username", recipient.getUsername());
      List<Object> result = reportService.generateInlineHtmlReport("html_inline", params);
      String html = (String) result.get(0);
      Map<String, byte[]> imageSource = (Map<String, byte[]>) result.get(1);
      emailService.sendHtmlEmail(recipient.getEmail(), html, imageSource);
    }
  }
}

Set an appropriate cron expression for com.juliuskrah.inline-cron and that’s all folks.

Conclusion

In this post we learned how to generate HTML mail with JasperReports with and without inline images.
As usual you can find the full example to this guide in the github repository. Until the next post, keep doing cool things :+1:.