Generate static websites using Java and Maven (jbake)

Time:2021-9-1

Use jbake (“MVN generate resources”) to build your static website or blog. Use layouts, macros, and data files.

We migrated the entire www.optaplaner.org website (1399 files) to build using Java and Maven instead of ruby and rake. On the surface, nothing has changed. But in the source code, it is a game changer for our team of Java developers.

Our Java team can now easily contribute to the website. Within hours of completing the migration, one of our developers had submitted a submission that did not want to touch the previous source code with a ten foot pole.

We set up this website.
We built this site on Java and Maven.
We set up this website.
We set up this website on jbake and FreeMarker.

Why use static site builder?

The static website builder converts templates and content files into static HTML / JS / CSS websites. For projects like ours, this has many advantages over the content management system (CMS):

Hosting is cheap. GitHub pages even host static websites for free.

The source file enters git for backup and history recording.

The source file is in plain text format:

Changes occur in the form of pull requests for appropriate review and CI validation.

The source code is open in our IDE, which encourages refactoring with the code. This reduces stale content.

Awestruct has served us for many years. But due to the lack of activity, it’s time to upgrade.

Why jbake?

Because we are Java programmers.

There are several good static website generators, such as Jekyll (Ruby) and Hugo (go). We chose jbake (Java) because:

Our website is now built using Maven (MVN generate resources).

Nothing needs to be installed. Not even jbake. Everyone builds with the same version of jbake, such as POM. XML

And it’s fast: even if MVN clean builds 150 output pages on my machine, it only takes 20 seconds.

The following is all Java.

Writing conditional expressions is simple. API (string. Substring(),…) is familiar. The date format (d mmmm yyyy) and regular expressions behave as expected.

Most importantly, the error message is clear.

For eight years, I have written this website in awestruct (Ruby). But I’ve never taken the time to learn Ruby well, so every change takes hours of trial and error. I can’t just read the error message and fix it. It’s not Ruby’s fault. That’s because I never spent a few days really learning ruby. With jbake, I can fix errors in a short time: there is no need to try again and again.

What is jbake?

Jbake is a static website generator with many options:

Build using Maven or gradle.

We chose Maven because all our repos were built with Maven (although the two optaplaner quickstarts were also built with gradle because optaplaner also supports gradle).

Write content in asciidoc, markdown, or HTML.

We chose asciidoc because it is richer and more reliable than markdown. In addition, all our documents are written in asciidoc.

Create a template using FreeMarker, thymeleaf, or groovy.

We chose FreeMarker because it is a powerful and practical template engine.

Tips and tricks

These are common tasks for building advanced static websites and how to implement each task in jbake FreeMarker. You can even call these jbake design patterns:

Rendering shared content using macros

Almost all our templates display the same latest version panel:

Latest release

FreeMarker templates are ideal for avoiding repeating yourself (dry):
Templates / macros.ftl is created using macros that output HTML:

<#macro latestReleases>
    <div class="panel panel-default">
        <div class="panel-heading">Latest release</div>
        ...
    </div>
</#macro>

Then use it in the *. FTL template:

<#import "macros.ftl" as macros>
...
<div class="row">
    <div class="col-md-9">
        ...
    </div>
    <div class="col-md-3">
        <@macros.latestReleases/>
    </div>
</div>

Use data files to add videos, events, or other volatile data

Some data changes are too frequent to be maintained in content or template files:

A data file, such as a simple *. YML file, can well store such volatile data:

Create data / videos.yml:

- youtubeId: blK7gxqu2B0
title: "Unit testing constraints"
...

- youtubeId: gIaHtATz6n8
title: "Maintenance scheduling"
...

- youtubeId: LTkoaBk-P6U
title: "Vaccination appointment scheduling"
...

Then use it in the FTL template:


<#assign videos = data.get('videos.yml').data>

<div class="panel panel-default">
    <div class="panel-heading">Latest videos</div>
    <div class="panel-body">
        <ul>
            <#list videos[0..6] as video>
                <li>
                    <a href="https://youtu.be/${video.youtubeId}">${video.title}</a>
                </li>
            </#list>
        </ul>
    </div>
</div>

Layout inheritance

All HTML pages typically share the same HTML header (metadata), header (navigation), and footer. These are well suited to the base.ftl layout and are extended by all other templates:
Generate static websites using Java and Maven (jbake)
Although most content uses normalbase.ftl, usecasebase.ftl has separate templates for all use case pages, such as vehicle routing issues (VRP), maintenance planning, and shift scheduling.

Use the macro < \ #nested > with to build layout inheritance:

Create templates / base.ftl:


<#macro layout>
    <html>
        <head>
          ...
        </head>
        <body>
            <div>
                ... <#-- header -->
            </div>
            <#nested>
            <div>
              ... <#-- footer -->
            </div>
        </body>
    </html>
</#macro>

Extend its templates / usecasebase.ftl and introduce the custom attribute related_ tag:


<#import "base.ftl" as parent>

<@layout>${content.body}</@layout>

<#macro layout>
    <@parent.layout>
        <h1>${content.title}</h1>
        <#nested>
        <h2>Related videos</h2>
        <#assign videos = data.get('videos.yml').data>
        <#assign relatedVideos = videos?filter(video -> video.tags.contains(content.related_tag))>
        <ul>
            <#list relatedVideos as video>
                <li><a href="https://youtu.be/${video.youtubeId}">${video.title}</a></li>
            </#list>
        </ul>
    </@parent.layout>
</#macro>

Create content / vehicleroutingproblem.adoc, use the template and set the related_ Use case page of tag attribute:


= Vehicle Routing Problem
:jbake-type: useCaseBase
:jbake-related_tag: vehicle routing

The Vehicle Routing Problem (VRP) optimizes the routes of delivery trucks,
cargo lorries, public transportation (buses, taxi's and airplanes)
or technicians on the road, by improving the order of the visits.
This routing optimization heavily reduces driving time and fuel consumption compared to manual planning:

...

start
Try it yourself. To build the www.optaplaner.org website, run the following command:

$ git clone https://github.com/kiegroup/o…

$ cd optaplanner-website
$ mvn clean generate-resources

$ firefox target/website/index.html
Or looksource code