# Jte Spring Boot

> Setup and configure JTE (Java Template Engine) in a Spring Boot 3 or Spring Boot 4 Maven project following specific performance and code-style standards. Use when adding JTE to a project or fixing configuration.

- Skill: `hi-rullah/jte-spring-boot` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add hi-rullah/jte-spring-boot`
- Raw SKILL.md: https://api.skillmd.com/api/skills/hi-rullah/jte-spring-boot/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: hi-rullah (https://skillmd.com/u/hi-rullah)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/hi-rullah/jte-spring-boot

---


# 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.xml` is 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 update `pom.xml` without risking XML corruption.

**Manual POM Editing Rules:**
If modifying `pom.xml` directly:
1. **Dependencies placement**: Locate the existing `</dependencies>` closing tag, and insert the dependency blocks directly **before** it. Do NOT create duplicate `<dependencies>` tags.
2. **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>`.
3. **Integrity check**: Verify the XML structure after editing to ensure no unmatched tags or malformed syntax.

**Dependencies (Spring Boot 3.x):**
```xml
<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):**
```xml
<dependency>
    <groupId>gg.jte</groupId>
    <artifactId>jte-spring-boot-starter-4</artifactId>
    <version>3.2.4</version>
</dependency>
```

**Maven Plugin (3.2.4):**
```xml
<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):**
```bash
# 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:**
1. Create the template root: `mkdir -p src/main/jte`.
2. Create the root indicator: `touch src/main/jte/.jteroot`.
3. 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`):**
```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`):**
```properties
# Use precompiled Java bytecode in production
gg.jte.developmentMode=false
gg.jte.usePrecompiledTemplates=true
```

**Base Config (`src/main/resources/application.properties`):**
```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):**
```javascript
// ❌ WRONG: backticks and ${} cause syntax or escaping conflicts with JTE
const message = `Welcome to ${title}!`;
```

**DO (String Concatenation with `+`):**
```javascript
// ✅ 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`):**
```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`):**
```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`):**
```javascript
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:*
```jte
@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`):**
```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`):**
```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.xml` uses the matching starter (`jte-spring-boot-starter-3` or `jte-spring-boot-starter-4`) with JTE 3.2.4 and plugin 3.2.4.
- [ ] `.jteroot` exists in `src/main/jte`.
- [ ] `gg.jte.developmentMode` is set correctly for the profile.
- [ ] All JS blocks use `+` for concatenation.

