Konstantin Ryadov JVM engineer turned AI engineer. Things I have built.

How FreeMarker helps you build an HTML report from a template quickly (+ converting it to PDF)

Apache FreeMarker is a Java library (Apache 2.0 licence) that generates all kinds of text output (HTML, e-mails, configuration files, source code and so on) from templates and changing input data.

Templates are written in the FreeMarker Template Language (FTL), which is readable not only by a programmer but by, say, a front-end developer as well.

The data model can be, for example, a HashMap with Strings, Numbers and POJOs inside, or, with a little more effort, JSON.

The point is that the FreeMarker engine lets you use not only a set of standard functions and constructs (conditions, loops, number and string handling) but also extend a template with calls to your own functions (for example, spelling a number out in words, transliterating a string and so on). On top of that there is code completion in IDEA and Eclipse, which, if you use them, lets you develop "faster".

How FreeMarker works: template + data model = output

So, to generate a report you need to:

  1. Configure the engine:
// Set the configuration version
Configuration cfg = new Configuration(Configuration.VERSION_2_3_25);

// Where the template files live
cfg.setDirectoryForTemplateLoading(new File("/where/you/store/templates"));

// Template encoding
cfg.setDefaultEncoding("UTF-8");

// How errors are handled
// Use TemplateExceptionHandler.HTML_DEBUG_HANDLER.
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
  1. Create a data model:
(root)
  |
  +- user = "Big Joe"
  |
  +- latestProduct
      |
      +- url = "products/greenmouse.html"
      |
      +- name = "green mouse"

Like this:

// The root object is a HashMap, but it could be a JavaBean.
Map<String, Object> root = new HashMap<>();

// Put "user" into "root"
root.put("user", "Big Joe");

// Create "latestProduct"
Product latest = new Product();
latest.setUrl("products/greenmouse.html");
latest.setName("green mouse");
// and put "latestProduct" into "root"
root.put("latestProduct", latest);

The Java bean itself (the class must be public and have the corresponding getters):

public class Product {
    private String url;
    private String name;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  1. Get the template from the Configuration instance (the file test.ftlh must exist in /where/you/store/templates, which we set in the first step):
Template temp = cfg.getTemplate("test.ftlh");

The template file itself:

<html>
<head>
  <title>Welcome!</title>
</head>
<body>
  <h1>Welcome ${user}!</h1>
  <p>Our latest product:
  <a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>
</html>
  1. Merge the template with the data and print it to stdout:
Writer out = new OutputStreamWriter(System.out);
temp.process(root, out);
  1. The result:
<html>
<head>
  <title>Welcome!</title>
</head>
<body>
  <h1>Welcome John Doe!</h1>
  <p>Our latest product:
  <a href="products/greenmouse.html">green mouse</a>!
</body>
</html>

Further reading:

  1. The list of built-in functions
  2. How to write your own function
  3. How to convert the resulting HTML, for example, to PDF with Flying Saucer:
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;

public static byte[] html2pdf(String html, String fontsPath) throws IOException,
                                                              DocumentException {
    ITextRenderer renderer = new ITextRenderer();
    loadFonts(renderer.getFontResolver(), fontsPath);
    renderer.setDocumentFromString(html);
    renderer.layout();

    ByteArrayOutputStream os = new ByteArrayOutputStream();
    renderer.createPDF(os);

    return os.toByteArray();
}

Originally published in Russian on LiveJournal.

All posts