Spring MVC + Mustache JS template example

spring-mustache-image

In this tutorial, we will show you a Spring MVC + Mustache JS template web application example.

Technologies used :

  1. Spring 4.2.4.RELEASE
  2. mustache.js
  3. JDK 1.8.0_66
  4. Jackson 2.7
  5. Eclipse 4.3
  6. Tomcat 8
  7. Maven 3
  8. Bootstrap 3
Note
This solution is still working, but recommend this simple Spring Boot + JMustache solution.

In Spring, we can use ScriptTemplateConfigurer to integrate most of the JS templating library, for example Mustache, Handlebars or React. Refer to this official Spring script template documentation.

P.S ScriptTemplateConfigurer has been supported since Spring 4.2

Note
This example will run “Mustache JS” on the server side, using the built in Java 8 Nashorn Javascript engine. An isomorphic javascript example.

1. Project Structure

A standard Maven folder structure.

spring-mustache-project-directory

2. pom.xml

A Maven pom.xml file to declare dependencies and some useful plugins.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
	http://maven.apache.org/maven-v4_0_0.xsd">

	<modelVersion>4.0.0</modelVersion>
	<groupId>com.mkyong</groupId>
	<artifactId>spring-mvc-mustache</artifactId>
	<packaging>war</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>spring mvc mustache</name>

	<properties>
		<jdk.version>1.8</jdk.version>
		<spring.version>4.2.4.RELEASE</spring.version>
		<servletapi.version>3.1.0</servletapi.version>
        <logback.version>1.1.3</logback.version>
        <jcl.slf4j.version>1.7.12</jcl.slf4j.version>
        <jackson.version>2.7.0</jackson.version>
	</properties>

	<dependencies>

		<!-- Spring and exclude commons log -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
		</dependency>

		<!-- Add slf4j Logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${jcl.slf4j.version}</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
        </dependency>

		<!-- compile only, deployed container will provide this -->
		<!--  no web.xml project need this to start app -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>${servletapi.version}</version>
			<scope>provided</scope>
		</dependency>

		<!-- JSON -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>

	</dependencies>

	<build>

		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.3</version>
				<configuration>
					<source>${jdk.version}</source>
					<target>${jdk.version}</target>
				</configuration>
			</plugin>

			<plugin>
				<groupId>org.eclipse.jetty</groupId>
				<artifactId>jetty-maven-plugin</artifactId>
				<version>9.2.11.v20150529</version>
				<configuration>
					<scanIntervalSeconds>10</scanIntervalSeconds>
					<webApp>
						<contextPath>/spring</contextPath>
					</webApp>
				</configuration>
			</plugin>

			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-eclipse-plugin</artifactId>
				<version>2.9</version>
				<configuration>
					<downloadSources>true</downloadSources>
					<downloadJavadocs>true</downloadJavadocs>
					<wtpversion>2.0</wtpversion>
					<wtpContextName>spring</wtpContextName>
				</configuration>
			</plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>

		</plugins>
	</build>

</project>

3. Integrate Spring + Mustache JS

To integrate Mustache script template as the Spring view template, configure both ScriptTemplateConfigurer and ViewResolver. See comment for self-explanatory.

SpringWebConfig.java

package com.mkyong.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.script.ScriptTemplateConfigurer;
import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver;

@EnableWebMvc
@Configuration
@ComponentScan({ "com.mkyong.web.controller" })
public class SpringWebConfig extends WebMvcConfigurerAdapter {
 
    @Bean
    public ScriptTemplateConfigurer configurer() {
        ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
        
        //1. Nashorn jdk8 script engine.
        configurer.setEngineName("nashorn");
        
        //2. Add mustache.min.js and custom render.js to Nashorn
        configurer.setScripts("/static/js/mustache.min.js", "/static/js/render.js");
        
        //3. Ask Nashorn to run this function "render()"
        configurer.setRenderFunction("render");
        return configurer;
    }

    //Define where is Mustache template, in classpath level.
  	// If view "hello" is returned, Mustache temple will be '/static/templates/hello.html'
    @Bean
    public ViewResolver viewResolver() {
        ScriptTemplateViewResolver viewResolver = new ScriptTemplateViewResolver();
        viewResolver.setPrefix("/static/templates/");
        viewResolver.setSuffix(".html");
        return viewResolver;
    }

	//add static resources like js or css
	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
	}

}

4. Spring Controller + Mustache Template

4.1 A Spring controller to pass data to the Mustache template.

HelloController.java

package com.mkyong.web.controller;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mkyong.web.model.User;

@Controller
public class HelloController {

    private static final Logger logger = LoggerFactory.getLogger(HelloController.class);

    private static final ObjectMapper om = new ObjectMapper();

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public ModelAndView printWelcome(HttpServletRequest request) {

        ModelAndView result = new ModelAndView();
        result.addObject("resources", request.getContextPath() + "/resources");
        result.addObject("logo", "mkyong.com");
        result.addObject("title", "Spring MVC + Mustache template");
        result.addObject("jumbo-title", "Spring MVC + Mustache template");
        result.addObject("jumbo-desc", "Maven + Spring MVC + Mustache JS, ScriptTemplate example.");

        //1. Test data type
        result.addObject("id", 100);
        result.addObject("username", "mkyong");

        //2. Test List
        result.addObject("scriptTemplates", getScriptTemplate());

        //3. Test Object
        result.addObject("user", new User("abc@gmail.com", 0));

        //4. Test List<Object>
        List<User> list = new ArrayList<>();
        list.add(new User("aaa@gmail.com", 1));
        list.add(new User("bbb@yahoo.com", 2));
        list.add(new User("ccc@hotmail.com", 3));
        result.addObject("users_json", convertObjectToJson(list));

        result.addObject("users", list);

        result.setViewName("hello");
        return result;

    }

    private List<String> getScriptTemplate() {

        List<String> scriptTemplates = new ArrayList<>();
        scriptTemplates.add("Handlebars");
        scriptTemplates.add("Mustache");
        scriptTemplates.add("React");
        scriptTemplates.add("EJS");
        scriptTemplates.add("ERB");
        scriptTemplates.add("String templates");
        return scriptTemplates;

    }

    //Jackson2 - Convert Java Object to JSON format
    public static String convertObjectToJson(Object obj) {
        String result = "";
        try {
            result = om.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            logger.error("Error In JSON conversion : {}", e);
        }
        return result;
    }

}

4.2 A Mustache template file to display the data from the above Spring controller.

/static/templates/hello.html

<!DOCTYPE html>
<html lang="en">
<head>
    <title>{{title}}</title>
    <link href="{{{resources}}}/core/css/bootstrap.min.css" rel="stylesheet">
    <link href="{{{resources}}}/core/css/bootstrap-theme.min.css" rel="stylesheet">
    <link href="{{{resources}}}/core/css/hello.css" rel="stylesheet">
</head>

<body role="document">

<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
        <div class="navbar-header">
            <a class="navbar-brand" href="#">{{logo}}</a>
        </div>
    </div>
</nav>

<div class="container theme-showcase" role="main">

    <div class="jumbotron">
        <h1>{{jumbo-title}}</h1>
        <p>{{jumbo-desc}}</p>
    </div>

    <div class="row">
        <div class="col-sm-6">
            <div class="page-header">
                <h1>1. Data Types</h1>
            </div>
            <p>
            <div>
                {{#id}}
                Hello {{username}}, id : {{.}}
                {{/id}}
            </div>
            </p>
        </div>
        <div class="col-sm-6">
            <div class="page-header">
                <h1>2. List</h1>
            </div>
            <p>
            <pre>{{scriptTemplates}}</pre>
            <ol>
                {{#scriptTemplates}}
                <li>{{.}}</li>
                {{/scriptTemplates}}
            </ol>
            </p>
        </div>
    </div>

    <div class="row">
        <div class="col-sm-6">
            <div class="page-header">
                <h1>3. User Object</h1>
            </div>
            <p>
            <div>
                <pre>{{user}}</pre>
                <ol>
                    {{#user}}
                    <li>email- {{user.email}}</li>
                    <li>loginFailed- {{user.loginFailed}}</li>
                    {{/user}}
                </ol>
            </div>
            </p>
        </div>
        <div class="col-sm-6">
            <div class="page-header">
                <h1>4. List of User Objects (JSON)</h1>
            </div>
            <p>
            <pre>{{user_json}}</pre>
            {{#users_json}}
            <ol>
                <li>email- {{email}}</li>
                <li>loginFailed- {{loginFailed}}</li>
            </ol>
            {{/users_json}}

            </p>
        </div>
    </div>

    <div class="row">
        <div class="col-sm-6">
            <div class="page-header">
                <h1>5. List of User Objects (RAW)</h1>
            </div>
            <p>
                No idea how to loop it...
            <pre>{{users}}</pre>
            {{#users}}
            <ol>
                <li>email- {{email}}</li>
                <li>loginFailed- {{loginFailed}}</li>
            </ol>
            {{/users}}
            </p>
        </div>
        <div class="col-sm-6">
            <div class="page-header">
                <h1></h1>
            </div>
            <p>
            </p>
        </div>
    </div>
    
</div>

<div class="container">
    <hr>
    <footer>
        <p>© Mkyong.com 2016</p>
    </footer>
</div>

<script type="text/javascript" src="{{{resources}}}/core/js/hello.js"></script>
<script type="text/javascript" src="{{{resources}}}/core/js/jquery-1.12.0.min.js"></script>
<script type="text/javascript" src="{{{resources}}}/core/js/bootstrap.min.js"></script>

</body>
</html>

5. Javascript ‘render.js’

When a view is returned, Spring will run this function render(template, model, url). But, not all Java objects or data types are supported in Javascript, conversion is required, especially the List<Object>.

Summary :

  1. For primitive data type, there is no need for conversion.
  2. For java.lang.Iterable, convert it with Java.from().
  3. For List<User>, convert it into JSON formatted string with Jackson, and convert it to a JS object via JSON.parse. This trick should work in any Java object <--> JavaScript conversion.
/static/js/render.js

/**
 * Bindings to use mustache.js with Nashorn.
 */

/**
 * String templates: the Mustache template content. e.g hello.html
 * Map model: the model in controller
 * String url: the templates url (since 4.2.2)
 */
function render(template, model, url) {
	return Mustache.render(template, toJsonObject(model));
}

// Some Java objects may not support in JS directly, need conversion.
function toJsonObject(model) {
    var o = {};
    for (var k in model) {

        //Convert Object String to Javascript JSON
        if (k.indexOf("_json") > -1) {
            o[k] = JSON.parse(model[k]);
            continue;
        }

        // Convert Iterable like List to real JSON array
        if (model[k] instanceof Java.type("java.lang.Iterable")) {
            o[k] = Java.from(model[k]);
        }
        else {
            o[k] = model[k];
        }
    }
    return o;
}

6. Misc Classes

6.1 Create a WebInitializer class to create a no web.xml web application.

MyWebInitializer

package com.mkyong.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import com.mkyong.config.SpringWebConfig;

public class MyWebInitializer extends
		AbstractAnnotationConfigDispatcherServletInitializer {

	@Override
	protected Class<?>[] getServletConfigClasses() {
		return new Class[] { SpringWebConfig.class };
	}

	@Override
	protected String[] getServletMappings() {
		return new String[] { "/" };
	}

	@Override
	protected Class<?>[] getRootConfigClasses() {
		return null;
	}

}

6.2 User Object.

User.java

package com.mkyong.web.model;

public class User {

    String email;
    int loginFailed;

    public User(String email, int loginFailed) {
        this.email = email;
        this.loginFailed = loginFailed;
    }

   //setters and getters
}

7. Demo

7.1 http://localhost:8080/spring/

spring-mustache-demo1

7.2 http://localhost:8080/spring/

spring-mustache-demo2

8. How to run this project?

8.1 Clone the source code from Github.


$ git clone https://github.com/mkyong/spring-mvc-mustache-js-template

8.2 Run the embedded Jetty container.


$ mvn jetty:run

8.3 Visit URL : http://localhost:8080/spring/

Download Source Code

Download – spring-mvc-mustache.zip (127 KB)

References

  1. Mustache – Logic-less templates
  2. Mustache – Javascript
  3. Spring IO – View Template
  4. Isomorphic templating with Spring Boot, Nashorn and React
  5. Java 8 Nashorn tutorial
  6. Spring React example
  7. Yet another Spring Mustache solution
  8. Isomorphic JavaScript

About the Author

author image
mkyong
Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities.

Comments

avatar
2 Comment threads
0 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
2 Comment authors
Saurabh?? Recent comment authors
newest oldest most voted
Saurabh
Guest
Saurabh

How to get this folder structure in Eclipse-STS.

??
Guest
??

nice