Spring Framework has built-in integration for using Spring MVC with any templating library that can be executed based on the specification scripting engine JSR-223 in Java. We tested the following templating libraries on various script execution engines:

The basic rule for integrating any other script engine is that it must implement the ScriptEngine and Invocable interfaces.

Requirements

You must provide a script execution engine in your classpath, the details of which depend on the script execution engine:

  • A JavaScript processing engine called Nashorn ships with Java 8+. It is highly recommended that you use the latest update available.

  • JRuby must be added as a dependency to support the Ruby language.

  • Jython must be added as a dependency to provide Python language support.

  • To provide support for Kotlin scripts, you should add the dependency org.jetbrains.kotlin:kotlin-script-util and the file META-INF/services/javax.script.ScriptEngineFactory, containing the string org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory. For more information, see this example.

You need to have a script template library. One way to do this for JavaScript is WebJars.

Script templates

You can declare a ScriptTemplateConfigurer bean to specify which script engine to use, which script files to load, which function to call to render templates, and so on. The following example uses Mustache templates and a JavaScript engine called Nashorn:

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.scriptTemplate();
    }
    @Bean
    public ScriptTemplateConfigurer configurer() {
        ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
        configurer.setEngineName("nashorn");
        configurer.setScripts("mustache.js");
        configurer.setRenderObject("Mustache");
        configurer.setRenderFunction("render");
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.scriptTemplate()
    }
    @Bean
    fun configurer() = ScriptTemplateConfigurer().apply {
        engineName = "nashorn"
        setScripts("mustache.js")
        renderObject = "Mustache"
        renderFunction = "render"
    }
}

The following example shows the same way of organizing in XML:

<mvc:annotation-driven/>
<mvc:view-resolvers>
    <mvc:script-template/>
</mvc:view-resolvers>
<mvc:script-template-configurer engine-name="nashorn" render-object="Mustache" render-function="render">
    <mvc:script location="mustache.js"/>
</mvc:script-template-configurer>

The controller will be the same for Java and XML configurations, as shown in the following example:

Java
@Controller
public class SampleController {
    @GetMapping("/sample")
    public String test(Model model) {
        model.addAttribute("title", "Sample title");
        model.addAttribute("body", "Sample body");
        return "template";
    }
}
Kotlin
@Controller
class SampleController {
    @GetMapping("/sample")
    fun test(model: Model): String {
        model["title"] = "Sample title"
        model["body"] = "Sample body"
        return "template"
    }
}

The following example shows the Mustache template:

<html>
    <head>
        <title>{{title}}</title>
    </head>
    <body>
        <p>{{body}}</p>
    </body>
</html>

The visualization function is called with the following parameters:

  • String template: Template content

  • Map model: View model

  • RenderingContext renderingContext: RenderingContext, which provides access to the application context, locale, template loader, and URL (since version 5.0).

Mustache.render() is natively compatible with this signature, so you can call it directly.

If your templating technology requires some customization, you can pass a script that implements a custom visualization function. For example, Handlerbars requires templates to be compiled before using them and requires polyfill to emulate some browser capabilities that are not available in the server-side scripting engine.

The following example shows how to do this:

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.scriptTemplate();
    }
    @Bean
    public ScriptTemplateConfigurer configurer() {
        ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
        configurer.setEngineName("nashorn");
        configurer.setScripts("polyfill.js", "handlebars.js", "render.js");
        configurer.setRenderFunction("render");
        configurer.setSharedEngine(false);
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.scriptTemplate()
    }
    @Bean
    fun configurer() = ScriptTemplateConfigurer().apply {
        engineName = "nashorn"
        setScripts("polyfill.js", "handlebars.js", "render.js")
        renderFunction = "render"
        isSharedEngine = false
    }
}
Setting the sharedEngine property to false is necessary when using thread-unsafe script execution engines with template libraries that are not calculated for parallelism, for example, Handlebars or React running on Nashorn. In this case, Java SE 8 update 60 is required due to this bug, but It is generally recommended to use the latest Java SE patch release anyway.

polyfill.js defines only the window object that Handlebars needs to work properly, as follows:

var window = {};

This basic implementation of render.js compiles the template before using it. A production-ready implementation must also store any reusable cached templates or precompiled templates. This can be done on the script side (and handle any customization you need - for example, managing the template engine configuration). The following example shows how to do this:

function render(template, model) {
    var compiledTemplate = Handlebars.compile(template);
    return compiledTemplate(model);
}

Check out the Spring Framework unit tests, Java and resources to view more configuration examples.