Close

Spring MVC - Mustache View

[Last Updated: Oct 8, 2026]

This example shows how to use the Mustache template engine as a view technology in Spring MVC. Mustache is a logic-less template language: templates contain only tags that are filled in from the model, and no embedded code.

Version compatibility note:

  • mustache-spring-view has not been updated since version 1.4 (November 2016) and it targets the old javax.servlet API. Spring Framework 6 moved to the Jakarta namespace, and Spring Framework 7 requires Jakarta EE 11 (Servlet 6.1, so Tomcat 11+ or Jetty 12.1+). The library mustache-spring-view is therefore expected to work only with Spring Framework 5.x (example here)
  • Spring Framework itself has no built-in Mustache support. In a Spring Boot application, use spring-boot-starter-mustache (based on JMustache), which auto-configures the view resolver and uses the .mustache suffix by default.
  • In a plain Spring MVC (Spring 6+) application without Boot, you would need to write a small custom view resolver on top of a Mustache library such as JMustache or Mustache.java, or choose a template engine with built-in Spring support.
  • Following example demonstrates how to use Mustache library in the latest version of Spring MVC.

Example

Additional Maven Dependencies

pom.xml

<dependency>
   <groupId>com.github.spullara.mustache.java</groupId>
   <artifactId>compiler</artifactId>
   <version>0.9.14</version>
</dependency>

Java Config Class

package com.logicbig.example;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.UrlBasedViewResolver;

@EnableWebMvc
@Configuration
@ComponentScan
public class MyWebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        UrlBasedViewResolver resolver = new UrlBasedViewResolver();
        resolver.setViewClass(MustacheTemplateView.class);
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".html");
        resolver.setContentType("text/html;charset=UTF-8");
        registry.viewResolver(resolver);
    }
}

MustacheTemplateView

package com.logicbig.example;

import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.MustacheFactory;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.view.AbstractTemplateView;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.Map;

public class MustacheTemplateView extends AbstractTemplateView {

    private static final MustacheFactory FACTORY =
            new DefaultMustacheFactory();

    @Override
    protected void renderMergedTemplateModel(Map<String, Object> model,
                                             HttpServletRequest request,
                                             HttpServletResponse response)
            throws Exception {
        response.setContentType(getContentType());
        try (Reader reader = new InputStreamReader(
                getServletContext().getResourceAsStream(getUrl()),
                StandardCharsets.UTF_8)) {
            FACTORY.compile(reader, getUrl())
                   .execute(response.getWriter(), model)
                   .flush();
        }
    }
}

Spring Controller

package com.logicbig.example;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Currency;
import java.util.List;

@Controller
@RequestMapping("/")
public class CurrencyRateController {

    @RequestMapping
    public String handleRequest(Model model) {
        model.addAttribute("todayCurrencyRates", getTodayForexRates());
        return "forex-view";
    }

    private List<CurrencyRate> getTodayForexRates() {
        //dummy rates
        List<CurrencyRate> currencyRates = new ArrayList<>();
        LocalDateTime today = LocalDateTime.of(2026, 2, 1, 0, 0, 0);
        List<Currency> currencies = new ArrayList<>(Currency.getAvailableCurrencies());

        int size = currencies.size();
        size = Math.min(size, 10);
        for (int i = 0; i + 1 < size; i += 2) {
            String currencyPair = currencies.get(i) + "/" + currencies.get(i + 1);
            CurrencyRate cr = new CurrencyRate();
            cr.setCurrencyPair(currencyPair);
            cr.setDateTime(today);
            BigDecimal bidPrice = new BigDecimal((i + 1) * 2);
            bidPrice = bidPrice.setScale(3, RoundingMode.CEILING);
            cr.setBidPrice(bidPrice);
            BigDecimal askPrice = new BigDecimal((i + 1) * 3);
            askPrice = askPrice.setScale(3, RoundingMode.CEILING);
            cr.setAskPrice(askPrice);
            currencyRates.add(cr);
        }
        return currencyRates;
    }
}
package com.logicbig.example;

import java.math.BigDecimal;
import java.time.LocalDateTime;

public class CurrencyRate {
    private String currencyPair;
    private LocalDateTime dateTime;
    private BigDecimal askPrice;
    private BigDecimal bidPrice;
    .............
}

Mustache Template

src/main/webapp/WEB-INF/views/forex-view.html

<html>
<head>
<style>
table {
    border-collapse: collapse;
    width:100%;
}
table, td, th {
    border: 1px solid #999;
    padding: 5px;
}
</style>
</head>
<body>
<h2>Currency Rates</h2>
<table>
    <thead>
    <tr>
        <th>
            <label>Currency Pair</label>
        </th>
        <th>
            <label>Bid Price</label>
        </th>
        <th>
            <label>Ask Price</label>
        </th>
        <th>
            <label>Date</label>
        </th>
    </tr>
    </thead>
    <tbody>
    {{#todayCurrencyRates}}
    <tr>
        <td>
            {{currencyPair}}
        </td>
        <td>
            {{askPrice}}
        </td>
        <td>
            {{bidPrice}}
        </td>
        <td>
            {{dateTime}}
        </td>
    </tr>
    {{/todayCurrencyRates}}
    </tbody>
</table>
</body>
</html>

MustacheViewResolver converts the logical view name returned by the controller (forex-view) into the template path /WEB-INF/views/forex-view.html, using the configured prefix and suffix. In the template, {{#todayCurrencyRates}} ... {{/todayCurrencyRates}} is a section: it is repeated once for each element of the model attribute with that name, and inside it tags such as {{currencyPair}} are resolved against the current element. Because Mustache is logic-less, formatting (for example of the date) should be done in the controller or in a view model rather than in the template.

Two practical notes: a {{name}} tag HTML-escapes its value, and the triple form {{{name}}} outputs it unescaped, so use that only for trusted content. Also, setCache(false) in the configuration class makes the template reload on every request, which is convenient during development but should be true in production.

Running the example

To try examples, run embedded Jetty (configured in pom.xml of example project below):

mvn jetty:run

Output

$ curl -s "http://localhost:8080/"
<html>
<head>
<style>
table {
border-collapse: collapse;
width:100%;
}
table, td, th {
border: 1px solid #999;
padding: 5px;
}
</style>
</head>
<body>
<h2>Currency Rates</h2>
<table>
<thead>
<tr>
<th>
<label>Currency Pair</label>
</th>
<th>
<label>Bid Price</label>
</th>
<th>
<label>Ask Price</label>
</th>
<th>
<label>Date</label>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
MOP/KPW
</td>
<td>
3.000
</td>
<td>
2.000
</td>
<td>
2026-02-01T00:00
</td>
</tr>
<tr>
<td>
PGK/MTL
</td>
<td>
9.000
</td>
<td>
6.000
</td>
<td>
2026-02-01T00:00
</td>
</tr>
<tr>
<td>
DJF/PKR
</td>
<td>
15.000
</td>
<td>
10.000
</td>
<td>
2026-02-01T00:00
</td>
</tr>
<tr>
<td>
GYD/JOD
</td>
<td>
21.000
</td>
<td>
14.000
</td>
<td>
2026-02-01T00:00
</td>
</tr>
<tr>
<td>
CSD/XCG
</td>
<td>
27.000
</td>
<td>
18.000
</td>
<td>
2026-02-01T00:00
</td>
</tr>
</tbody>
</table>
</body>
</html>

Example Project

Dependencies and Technologies Used:

  • spring-webmvc 7.0.6 (Spring Web MVC)
     Version Compatibility: 3.2.9.RELEASE - 7.0.6Version List
    ×

    Version compatibilities of spring-webmvc with this example:

      javax.servlet-api:3.x
    • 3.2.9.RELEASE
    • 3.2.10.RELEASE
    • 3.2.11.RELEASE
    • 3.2.12.RELEASE
    • 3.2.13.RELEASE
    • 3.2.14.RELEASE
    • 3.2.15.RELEASE
    • 3.2.16.RELEASE
    • 3.2.17.RELEASE
    • 3.2.18.RELEASE
    • 4.0.0.RELEASE
    • 4.0.1.RELEASE
    • 4.0.2.RELEASE
    • 4.0.3.RELEASE
    • 4.0.4.RELEASE
    • 4.0.5.RELEASE
    • 4.0.6.RELEASE
    • 4.0.7.RELEASE
    • 4.0.8.RELEASE
    • 4.0.9.RELEASE
    • 4.1.0.RELEASE
    • 4.1.1.RELEASE
    • 4.1.2.RELEASE
    • 4.1.3.RELEASE
    • 4.1.4.RELEASE
    • 4.1.5.RELEASE
    • 4.1.6.RELEASE
    • 4.1.7.RELEASE
    • 4.1.8.RELEASE
    • 4.1.9.RELEASE
    • 4.2.0.RELEASE
    • 4.2.1.RELEASE
    • 4.2.2.RELEASE
    • 4.2.3.RELEASE
    • 4.2.4.RELEASE
    • 4.2.5.RELEASE
    • 4.2.6.RELEASE
    • 4.2.7.RELEASE
    • 4.2.8.RELEASE
    • 4.2.9.RELEASE
    • 4.3.0.RELEASE
    • 4.3.1.RELEASE
    • 4.3.2.RELEASE
    • 4.3.3.RELEASE
    • 4.3.4.RELEASE
    • 4.3.5.RELEASE
    • 4.3.6.RELEASE
    • 4.3.7.RELEASE
    • 4.3.8.RELEASE
    • 4.3.9.RELEASE
    • 4.3.10.RELEASE
    • 4.3.11.RELEASE
    • 4.3.12.RELEASE
    • 4.3.13.RELEASE
    • 4.3.14.RELEASE
    • 4.3.15.RELEASE
    • 4.3.16.RELEASE
    • 4.3.17.RELEASE
    • 4.3.18.RELEASE
    • 4.3.19.RELEASE
    • 4.3.20.RELEASE
    • 4.3.21.RELEASE
    • 4.3.22.RELEASE
    • 4.3.23.RELEASE
    • 4.3.24.RELEASE
    • 4.3.25.RELEASE
    • 4.3.26.RELEASE
    • 4.3.27.RELEASE
    • 4.3.28.RELEASE
    • 4.3.29.RELEASE
    • 4.3.30.RELEASE
    • 5.0.0.RELEASE
    • 5.0.1.RELEASE
    • 5.0.2.RELEASE
    • 5.0.3.RELEASE
    • 5.0.4.RELEASE
    • 5.0.5.RELEASE
    • 5.0.6.RELEASE
    • 5.0.7.RELEASE
    • 5.0.8.RELEASE
    • 5.0.9.RELEASE
    • 5.0.10.RELEASE
    • 5.0.11.RELEASE
    • 5.0.12.RELEASE
    • 5.0.13.RELEASE
    • 5.0.14.RELEASE
    • 5.0.15.RELEASE
    • 5.0.16.RELEASE
    • 5.0.17.RELEASE
    • 5.0.18.RELEASE
    • 5.0.19.RELEASE
    • 5.0.20.RELEASE
    • 5.1.0.RELEASE
    • 5.1.1.RELEASE
    • 5.1.2.RELEASE
    • 5.1.3.RELEASE
    • 5.1.4.RELEASE
    • 5.1.5.RELEASE
    • 5.1.6.RELEASE
    • 5.1.7.RELEASE
    • 5.1.8.RELEASE
    • 5.1.9.RELEASE
    • 5.1.10.RELEASE
    • 5.1.11.RELEASE
    • 5.1.12.RELEASE
    • 5.1.13.RELEASE
    • 5.1.14.RELEASE
    • 5.1.15.RELEASE
    • 5.1.16.RELEASE
    • 5.1.17.RELEASE
    • 5.1.18.RELEASE
    • 5.1.19.RELEASE
    • 5.1.20.RELEASE
    • 5.2.0.RELEASE
    • 5.2.1.RELEASE
    • 5.2.2.RELEASE
    • 5.2.3.RELEASE
    • 5.2.4.RELEASE
    • 5.2.5.RELEASE
    • 5.2.6.RELEASE
    • 5.2.7.RELEASE
    • 5.2.8.RELEASE
    • 5.2.9.RELEASE
    • 5.2.10.RELEASE
    • 5.2.11.RELEASE
    • 5.2.12.RELEASE
    • 5.2.13.RELEASE
    • 5.2.14.RELEASE
    • 5.2.15.RELEASE
    • 5.2.16.RELEASE
    • 5.2.17.RELEASE
    • 5.2.18.RELEASE
    • 5.2.19.RELEASE
    • 5.2.20.RELEASE
    • 5.2.21.RELEASE
    • 5.2.22.RELEASE
    • 5.2.23.RELEASE
    • 5.2.24.RELEASE
    • 5.2.25.RELEASE
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.3.3
    • 5.3.4
    • javax.servlet-api:4.x
    • 5.3.5
    • 5.3.6
    • 5.3.7
    • 5.3.8
    • 5.3.9
    • 5.3.10
    • 5.3.11
    • 5.3.12
    • 5.3.13
    • 5.3.14
    • 5.3.15
    • 5.3.16
    • 5.3.17
    • 5.3.18
    • 5.3.19
    • 5.3.20
    • 5.3.21
    • 5.3.22
    • 5.3.23
    • 5.3.24
    • 5.3.25
    • 5.3.26
    • 5.3.27
    • 5.3.28
    • 5.3.29
    • 5.3.30
    • 5.3.31
    • 5.3.32
    • 5.3.33
    • 5.3.34
    • 5.3.35
    • 5.3.36
    • 5.3.37
    • 5.3.38
    • 5.3.39
    • javax.* -> jakarta.*
      jakarta.servlet-api:6.x
      Java 17 min
    • 6.0.0
    • 6.0.1
    • 6.0.2
    • 6.0.3
    • 6.0.4
    • 6.0.5
    • 6.0.6
    • 6.0.7
    • 6.0.8
    • 6.0.9
    • 6.0.10
    • 6.0.11
    • 6.0.12
    • 6.0.13
    • 6.0.14
    • 6.0.15
    • 6.0.16
    • 6.0.17
    • 6.0.18
    • 6.0.19
    • 6.0.20
    • 6.0.21
    • 6.0.22
    • 6.0.23
    • 6.1.0
    • 6.1.1
    • 6.1.2
    • 6.1.3
    • 6.1.4
    • 6.1.5
    • 6.1.6
    • 6.1.7
    • 6.1.8
    • 6.1.9
    • 6.1.10
    • 6.1.11
    • 6.1.12
    • 6.1.13
    • 6.1.14
    • 6.1.15
    • 6.1.16
    • 6.1.17
    • 6.1.18
    • 6.1.19
    • 6.1.20
    • 6.1.21
    • 6.2.0
    • 6.2.1
    • 6.2.2
    • 6.2.3
    • 6.2.4
    • 6.2.5
    • 6.2.6
    • 6.2.7
    • 6.2.8
    • 6.2.9
    • 6.2.10
    • 6.2.11
    • 6.2.12
    • 6.2.13
    • 6.2.14
    • 6.2.15
    • 6.2.16
    • 6.2.17
    • 6.2.18
    • 6.2.19
    • 7.0.0
    • 7.0.1
    • 7.0.2
    • 7.0.3
    • 7.0.4
    • 7.0.5
    • 7.0.6

    Versions in green have been tested.

  • compiler 0.9.14 (Implementation of mustache.js for Java)
  • jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
  • JDK 25
  • Maven 3.9.11

Spring MVC Mustache View Example Select All Download
  • spring-mustache-view-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • MyWebConfig.java
          • webapp
            • WEB-INF
              • views

    See Also

    Join