JTE for Spring Boot
Philosophy
JTE (Java Template Engine) is designed for speed and type safety. In this workflow, we prioritize:
- Type Safety: Leveraging JTE's pre-compilation to catch errors at build time.
- Maintainability: Using a strictly defined folder structure (
src/main/jte). - Standardization: Following consistent versioning (3.2.4) across the ecosystem.
- Legacy Compatibility: Preferring string concatenation (
+) in generated JavaScript to ensure compatibility across all target environments.
Workflow
1. Version Detection & Dependency Management
First, inspect pom.xml to determine the project's Spring Boot major version (check <parent> tag spring-boot-starter-parent or property <spring-boot.version>):
- If Spring Boot 3.x: use
jte-spring-boot-starter-3. - If Spring Boot 4.x: use
jte-spring-boot-starter-4. - Fallback: If
pom.xmlis missing or the Spring Boot version cannot be determined unambiguously, ask the user whether the target is Spring Boot 3 or Spring Boot 4 before proceeding.
[!IMPORTANT] Automated Setup (Preferred): Running
./scripts/scaffold.sh [project-dir]or./scripts/configure-pom.py [path/to/pom.xml]will automatically and safely updatepom.xmlwithout risking XML corruption.
Manual POM Editing Rules:
If modifying pom.xml directly:
- Dependencies placement: Locate the existing
</dependencies>closing tag, and insert the dependency blocks directly before it. Do NOT create duplicate<dependencies>tags. - Plugin placement: Locate the existing
<build><plugins>block and insert the plugin directly before</plugins>. If<build>is missing, add<build><plugins> ... </plugins></build>before</project>. - Integrity check: Verify the XML structure after editing to ensure no unmatched tags or malformed syntax.
Dependencies (Spring Boot 3.x):
<dependency>
<groupId>gg.jte</groupId>
<artifactId>jte-spring-boot-starter-3</artifactId>
<version>3.2.4</version>
</dependency>
<dependency>
<groupId>gg.jte</groupId>
<artifactId>jte</artifactId>
<version>3.2.4</version>
</dependency>
Dependencies (Spring Boot 4.x):
<dependency>
<groupId>gg.jte</groupId>
<artifactId>jte-spring-boot-starter-4</artifactId>
<version>3.2.4</version>
</dependency>
Maven Plugin (3.2.4):
<plugin>
<groupId>gg.jte</groupId>
<artifactId>jte-maven-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<id>jte-generate</id>
<phase>generate-sources</phase>
<goals><goal>generate</goal></goals>
<configuration>
<sourceDirectory>${project.basedir}/src/main/jte</sourceDirectory>
<contentType>Html</contentType>
<binaryStaticContent>true</binaryStaticContent>
<targetResourceDirectory>${project.build.outputDirectory}</targetResourceDirectory>
</configuration>
</execution>
</executions>
</plugin>
2. Infrastructure Setup
You can run the automated scaffolding script, or follow the manual steps:
Automated (Recommended):
# Run from skill directory targeting the project root (default is current directory)
./scripts/scaffold.sh [path-to-target-project]
This automatically creates src/main/jte, .jteroot, static folders, configures application.properties, and copies starter templates from resources/.
Manual:
- Create the template root:
mkdir -p src/main/jte. - Create the root indicator:
touch src/main/jte/.jteroot. - Initialize static resource folders:
mkdir -p src/main/resources/static/{js,css,fonts,images}.
3. Environment Configuration
Organize configuration cleanly using Spring Boot profile-specific property files:
Development (src/main/resources/application-dev.properties):
# Hot reload from source files during development
gg.jte.developmentMode=true
gg.jte.templateLocation=src/main/jte
gg.jte.templateSuffix=.jte
Production (src/main/resources/application-prod.properties):
# Use precompiled Java bytecode in production
gg.jte.developmentMode=false
gg.jte.usePrecompiledTemplates=true
Base Config (src/main/resources/application.properties):
# Set default active profile (switch to 'prod' for packaging/deployment)
spring.profiles.active=dev
4. JavaScript Guidelines
When writing JavaScript inside .jte files:
- No Template Literals: Do NOT use backticks (
`). - String Concatenation: Use the
+operator for dynamic values. - Quoting: Ensure server-side strings are wrapped in single or double quotes within the JS block.
DON'T (Template Literals):
// ❌ WRONG: backticks and ${} cause syntax or escaping conflicts with JTE
const message = `Welcome to ${title}!`;
DO (String Concatenation with +):
// ✅ CORRECT: quoted server-side variable concatenated with +
const message = "Welcome to " + "${title}" + "!";
initIndexPage("Loaded page: " + "${title}" + " successfully");
5. Starter Controller & Template Example
When creating an initial page or verifying the setup, follow this standard pattern:
Controller (HomeController.java):
package com.example.demo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String index(Model model) {
model.addAttribute("title", "Welcome to JTE");
return "index";
}
}
Default Starter Template (src/main/jte/index.jte):
@param String title
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>${title}</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<h1>${title}</h1>
<p>Your JTE application is up and running!</p>
<script src="/js/index.js"></script>
<script>
initIndexPage("Loaded page: " + "${title}" + " successfully");
</script>
</body>
</html>
Page JavaScript (src/main/resources/static/js/index.js):
function initIndexPage(message) {
console.log('Index page initialized: ' + message);
}
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM ready.');
});
Optional Layout (src/main/jte/layout/template.jte):
Only create or use this if the user asks for a shared/reusable layout:
@param String title
@param gg.jte.Content content
@param gg.jte.Content scripts = null
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<main>
${content}
</main>
@if(scripts != null)
${scripts}
@endif
</body>
</html>
6. Email Rendering Service (Optional)
JTE can be used as a high-performance replacement for Thymeleaf or FreeMarker to render HTML emails without requiring Spring WebMVC or an active HTTP request.
Email Template (src/main/jte/email/email-template.jte):
@param String recipientName
@param String subject
@param String messageBody
@param String actionUrl = null
@param String actionText = null
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>${subject}</title>
</head>
<body style="font-family: Arial, sans-serif; background-color: #f4f6f8; margin: 0; padding: 24px; color: #333333;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; overflow: hidden;">
<tr>
<td style="background-color: #1a73e8; padding: 24px; text-align: center; color: #ffffff;">
<h1 style="margin: 0; font-size: 20px;">${subject}</h1>
</td>
</tr>
<tr>
<td style="padding: 32px 24px;">
<p>Hello <strong>${recipientName}</strong>,</p>
<p>${messageBody}</p>
@if(actionUrl != null && actionText != null)
<div style="text-align: center; margin: 24px 0;">
<a href="${actionUrl}" style="background-color: #1a73e8; color: #ffffff; padding: 12px 24px; text-decoration: none; border-radius: 4px; font-weight: bold; display: inline-block;">${actionText}</a>
</div>
@endif
</td>
</tr>
</table>
</body>
</html>
Spring Email Service (EmailTemplateService.java):
package com.example.demo.email;
import gg.jte.TemplateEngine;
import gg.jte.output.StringOutput;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class EmailTemplateService {
private final TemplateEngine templateEngine;
public EmailTemplateService(TemplateEngine templateEngine) {
this.templateEngine = templateEngine;
}
public String renderEmail(String recipientName, String subject, String messageBody, String actionUrl, String actionText) {
StringOutput output = new StringOutput();
Map<String, Object> model = Map.of(
"recipientName", recipientName,
"subject", subject,
"messageBody", messageBody,
"actionUrl", actionUrl != null ? actionUrl : "",
"actionText", actionText != null ? actionText : ""
);
templateEngine.render("email/email-template.jte", model, output);
return output.toString();
}
}
Checklist
- Detected Spring Boot version (3.x vs 4.x) or clarified with user.
-
pom.xmluses the matching starter (jte-spring-boot-starter-3orjte-spring-boot-starter-4) with JTE 3.2.4 and plugin 3.2.4. -
.jterootexists insrc/main/jte. -
gg.jte.developmentModeis set correctly for the profile. - All JS blocks use
+for concatenation.