2

I am trying to use JTE template in Spring Boot 3.3 it is working during dev environment. During production deploy pre compilation is working but I am getting runtime error when I visit that route. For some reason template is not being found and I am getting this error. Resource not found.

This is my build.gradle.kts:

import kotlin.io.path.Path

plugins {
    java
    id("org.springframework.boot") version "3.3.0"
    id("io.spring.dependency-management") version "1.1.5"
    id("gg.jte.gradle") version("3.1.12")
}

group = "com.playpen"
version = "0.0.1-SNAPSHOT"

java {
    sourceCompatibility = JavaVersion.VERSION_21
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("gg.jte:jte-spring-boot-starter-3:3.1.12")
    implementation("gg.jte:jte:3.1.12")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.withType<Test> {
    useJUnitPlatform()
}

jte {
    precompile()
    sourceDirectory.set(Path("src/main/resources/templates"))
}

tasks.jar {
    dependsOn(tasks.precompileJte)
    from(fileTree("jte-classes") {
        include("**/*.class")
        include("**/*.bin") // Only required if you use binary templates
    })
}

This is my TemplateConfiguration:

import gg.jte.ContentType;
import gg.jte.TemplateEngine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
@Profile("prod")
class TemplateConfiguration {
    @Bean
    public TemplateEngine templateEngine() {
        return TemplateEngine.createPrecompiled(ContentType.Html);
    }
}

and here are my application.properties:

gg.jte.templateLocation=src/main/resources/templates
gg.jte.templateSuffix=.jte
gg.jte.developmentMode=true

and application-prod.properties:

gg.jte.usePrecompiledTemplates=true
gg.jte.developmentMode=false

Here is my controller:

@Controller
public class IndexController {
    @GetMapping("/")
    public String indexAction() {
        return "index.html";
    }
}

2 Answers 2

1

I was able to solve this issue after spending long hours reading the offcial docs, javadocs, and reading the source code for the spring-starter package. Here is what I understood so far.

The library can render templates outside of your java project that's why it doesn't treat your resources directory as a first class citizen. That's why you should consider your resources directory as a regular directory in your filesystem that you have to point to as any other directory.

Keep in mind that the TemplateEngine uses a CodeResolver to lookup for templates. In spring-starter it uses DirectoryCodeResolver by default which resolve templates by a given root directory on the filesystem. There is a second implementation (ResourceCodeResolver) that can be used to resolve templates in the resources directory.

Considering spring starter we can say that the library offers 3 modes:

Default

The default configuration based on the JteProperties sets the

gg.jte.template-suffix=.jte
gg.jte.use-precompiled-templates=false
gg.jte.template-location=src/main/jte
gg.jte.development-mode=false

This will lookup for templates under your project's source code under /src/main/jte/*.jte. When the TemplateEngine#render() is called. The template engine will compile the request template to a .class and caches the rendered class.

Cons (In production):

  1. Requires JDK to be present in the deployment machine to compile templates.
  2. The first render takes longer and consumes more resources based on the size and complexity of your templates.
  3. (This is more of an assumption) I'm not sure what is the cache invalidation policy but incase cached templates are invalidated there is a potential of multiple requests requesting the same template which resutls in mutliple compilation for the same template.

Development

You can enable the development mode by setting the:

gg.jte.development-mode=true
gg.jte.use-precompiled-templates=false

In this mode JTE will add a file watcher for each template called by the TemplateEngine#render(). The template is compiled the first time its rendered and the .class is cached until the template's hash is changed. This is usefull during development because it doesn't require restarting your appliaiton each time you make a change but it's not suitable for produciton.

Cons (In production):

  1. Same cons as for the Default mode
  2. Each template rendering request adds a file watcher to the template which can cause resource leaks and file locks that may cause unexpected issues.

Production

Production mode is enabled by setting:

gg.jte.development-mode=false
gg.jte.use-precompiled-templates=true

In this mode the TemplateEngine expects precompiled classes to be present on the classpath as commented in the JteAutoConfiguration#jteTemplateEngine. By calling the TemplateEngine#createPrecompiled() you are creating a new instance of a TemplateEngine that resolve template names by equivalent classes on the class path.

For example when calling TemplateEngine#render('/user/new-user.jte'). The template engine will try to load the equivalent precompiled class in the following package gg.jte.generated.precompiled.user.JtenewuserGenerated.class. If the ClassLoader fails to find the class in the class path an exception is occured. Thus you should precompile your templates using the maven or gradle plugins.

Solution:

Going back to your problem. You should remove your TemplateConfiguration. The same can be achieved by just updating your

application-prod.properties

gg.jte.usePrecompiledTemplates=true
gg.jte.developmentMode=false

1. MAVEN

pom.xml

<plugin>
   <groupId>gg.jte</groupId>
   <artifactId>jte-maven-plugin</artifactId>
   <version>${jte-gg.version}</version>
   <configuration>
      <sourceDirectory>${project.basedir}/src/main/resources/templates</sourceDirectory>
      <targetDirectory>${project.build.directory}/jte-classes</targetDirectory>
      <contentType>Html</contentType>
   </configuration>
   <executions>
      <execution>
         <id>precompile</id>
         <phase>process-classes</phase>
         <goals>
            <goal>precompile</goal>
         </goals>
      </execution>
   </executions>
</plugin>

After updating your pom.xml you need to run the following maven goals:

# required if you are packaging your app in your dev maching. This clean dev generated classes.
$mvn clean
# templates precompilation requires classes to be already compiled
$mvn compile
$mvn jte:precompile
$mvn package

2. GRADLE

jte {
    precompile()
    sourceDirectory.set(Path("src/main/resources/templates"))
}

Then execute

./gradlew clean
./gradlew precompileJte
./gradlew build
Sign up to request clarification or add additional context in comments.

1 Comment

the solution worked for me, but i had to set the target directory to be <targetDirectory>${project.build.directory}/classes</targetDirectory> to get around a TemplateNotFoundException
0

the rental folder must be named jte, now inside you create the subfolders you want1

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.