title: Struts 2 with Thymeleaf
body: <p>I want to use Thymeleaf as a template engine in my web app. I searched on Google to configure Themeleaf with struts2 and found some solutions with one <a href="https://github.com/codework/struts2-thymeleaf-plugin" rel="nofollow">plugin</a>. I have configured plugin in my app but I am not able to render page using plugin. instead I am getting error : </p>
  
  <pre><code>org.thymeleaf.exceptions.TemplateInputException: Error resolving template "/WEB-INF/templates/hello.html", template might not exist or might not be accessible by any of the configured Template Resolvers
      org.thymeleaf.TemplateRepository.getTemplate(TemplateRepository.java:246)
      org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1104)
      org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1060)
      org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1011)
      org.thymeleaf.TemplateEngine.process(TemplateEngine.java:955)
      com.struts.custom.resulttype.plugin.ThemeleafResult.execute(ThemeleafResult.java:51)
      com.opensymphony.xwork2.DefaultActionInvocation.executeResult(DefaultActionInvocation.java:374)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:278)
      org.apache.struts2.interceptor.debugging.DebuggingInterceptor.intercept(DebuggingInterceptor.java:256)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor.doIntercept(DefaultWorkflowInterceptor.java:176)
      com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      com.opensymphony.xwork2.validator.ValidationInterceptor.doIntercept(ValidationInterceptor.java:265)
      org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor.doIntercept(AnnotationValidationInterceptor.java:68)
      com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      com.opensymphony.xwork2.interceptor.ConversionErrorInterceptor.intercept(ConversionErrorInterceptor.java:138)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:211)
      com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:211)
      com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      com.opensymphony.xwork2.interceptor.StaticParametersInterceptor.intercept(StaticParametersInterceptor.java:190)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      org.apache.struts2.interceptor.MultiselectInterceptor.intercept(MultiselectInterceptor.java:75)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      org.apache.struts2.interceptor.CheckboxInterceptor.intercept(CheckboxInterceptor.java:90)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      org.apache.struts2.interceptor.FileUploadInterceptor.intercept(FileUploadInterceptor.java:243)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor.intercept(ModelDrivenInterceptor.java:100)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor.intercept(ScopedModelDrivenInterceptor.java:141)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      com.opensymphony.xwork2.interceptor.ChainingInterceptor.intercept(ChainingInterceptor.java:145)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      com.opensymphony.xwork2.interceptor.PrepareInterceptor.doIntercept(PrepareInterceptor.java:171)
      com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      com.opensymphony.xwork2.interceptor.I18nInterceptor.intercept(I18nInterceptor.java:176)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      org.apache.struts2.interceptor.ServletConfigInterceptor.intercept(ServletConfigInterceptor.java:164)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      com.opensymphony.xwork2.interceptor.AliasInterceptor.intercept(AliasInterceptor.java:192)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor.intercept(ExceptionMappingInterceptor.java:187)
      com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
      org.apache.struts2.impl.StrutsActionProxy.execute(StrutsActionProxy.java:54)
      org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:510)
      org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:77)
      org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:91)
  </code></pre>
  
  <p>This is implementation of <code>DefaultTemplateEngineProvider.java</code></p>
  
  <pre><code>public class DefaultTemplateEngineProvider implements TemplateEngineProvider {
    // HTML5 is the future!
    private String templateMode = "HTML5";
    private String characterEncoding = "UTF-8";
    // This will convert "home" to "/WEB-INF/templates/home.html"
    private String prefix = "/WEB-INF/templates/";
    private String suffix = ".html";
    private boolean cacheable = true;
  
    // Default template cache TTL to 1 hour. If not set, entries would live in
    // cache until expelled by LRU.
    private Long cacheTtlMillis = 3600000L;
  
    private TemplateEngine templateEngine;
  
    /**
     * Configure settings from the struts.xml or struts.properties, using sensible
     * defaults if values are not provided.
     */
    public void configure() {
      ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver();
      templateResolver.setTemplateMode(templateMode);
      templateResolver.setCharacterEncoding(characterEncoding);
      templateResolver.setPrefix(prefix);
      templateResolver.setSuffix(suffix);
      templateResolver.setCacheable(cacheable);
      templateResolver.setCacheTTLMs(cacheTtlMillis);
  
      templateEngine = new TemplateEngine();
      templateEngine.setTemplateResolver(templateResolver);
      templateEngine.setMessageResolver(new StrutsMessageResolver());
    }
  
  
    public TemplateEngine get() {
      if (templateEngine == null) {
        configure();
      }
  
      return templateEngine;
    }
  
    @Inject(value = "struts.thymeleaf.templateMode", required = false)
    public void setTemplateMode(String templateMode) {
      this.templateMode = templateMode;
    }
  
    @Inject(value = "struts.thymeleaf.encoding", required = false)
    public void setCharacterEncoding(String characterEncoding) {
      this.characterEncoding = characterEncoding;
    }
  
    @Inject(value = "struts.thymeleaf.prefix", required = false)
    public void setPrefix(String prefix) {
      this.prefix = prefix;
    }
  
    @Inject(value = "struts.thymeleaf.suffix", required = false)
    public void setSuffix(String suffix) {
      this.suffix = suffix;
    }
  
    @Inject(value = "struts.thymeleaf.cacheable", required = false)
    public void setCacheable(String cacheable) {
      this.cacheable = Boolean.parseBoolean(cacheable);
    }
  
    @Inject(value = "struts.thymeleaf.cacheTtlMillis", required = false)
    public void setCacheTtlMillis(String cacheTtlMillis) {
      this.cacheTtlMillis = Long.parseLong(cacheTtlMillis);
    }
  }
  </code></pre>
  
  <p>I have written custom result set and configured Thymeleaf as documented on official site. But getting same error. </p>
  
  <p>My Custom result type is</p>
  
  <pre><code>public class ThymeleafResult implements Result {
    private String defaultEncoding = "UTF-8";
    private TemplateEngineProvider templateEngineProvider;
    private String templateName;
  
    /**
     * The result parameter name to set the name of the template to.
     * &lt;p/&gt;
     * IMPORTANT! Struts2 will look for this field reflectively to determine which
     * parameter is the default. This allows us to have a simplified result
     * configuration. Don't remove it!
     */
    public static final String DEFAULT_PARAM = "templateName";
  
    public ThymeleafResult() {
    }
  
    public ThymeleafResult(String templateName) {
      this.templateName = templateName;
    }
  
  
    public void execute(ActionInvocation actionInvocation) throws Exception {
      TemplateEngine templateEngine = templateEngineProvider.get();
  
      HttpServletRequest request = ServletActionContext.getRequest();
      HttpServletResponse response = ServletActionContext.getResponse();
      ServletContext servletContext = ServletActionContext.getServletContext();
  
      Object action = actionInvocation.getAction();
  
      StrutsContext context = new StrutsContext(request, response, servletContext, action);
      response.setContentType("text/html");
      response.setCharacterEncoding(defaultEncoding);
      System.out.println(templateName);
      templateEngine.process(templateName, context, response.getWriter());
    }
  
    @Inject(StrutsConstants.STRUTS_I18N_ENCODING)
    public void setDefaultEncoding(String defaultEncoding) {
      this.defaultEncoding = defaultEncoding;
    }
  
    @Inject
    public void setTemplateEngineProvider(TemplateEngineProvider templateEngineProvider) {
      this.templateEngineProvider = templateEngineProvider;
    }
  
    public void setTemplateName(String templateName) {
      this.templateName = templateName;
    }
  </code></pre>
  
  <p>And struts.xml is</p>
  
  <pre class="lang-xml prettyprint-override"><code>&lt;struts&gt;
  
      &lt;constant name="struts.enable.DynamicMethodInvocation" value="false" /&gt;
      &lt;constant name="struts.devMode" value="true" /&gt;
      &lt;constant name="struts.multipart.maxSize" value="1000000000" /&gt;
      &lt;constant name="struts.custom.i18n.resources" value="ApplicationResources" /&gt;
       &lt;bean type="org.codework.struts.plugins.thymeleaf.spi.TemplateEngineProvider" name="default" class="org.codework.struts.plugins.thymeleaf.spi.DefaultTemplateEngineProvider" /&gt;
  
      &lt;package name="default" namespace="/" extends="struts-default"&gt;
          &lt;result-types&gt;
              &lt;result-type name="thymeleaf" class="org.codework.struts.plugins.thymeleaf.ThymeleafResult"/&gt;
          &lt;/result-types&gt;
          &lt;default-action-ref name="home"&gt;&lt;/default-action-ref&gt;
          &lt;action name="home" class="com.demo.example.Welcome"&gt;
               &lt;result name="success" type="thymeleaf"&gt;/WEB-INF/templates/hello.html&lt;/result&gt;
          &lt;/action&gt;
      &lt;/package&gt;
  &lt;/struts&gt;
  </code></pre>