Template Engine (java, written by Claude Code)
envgap__claude-code__java-t2-35
Written by a coding agent; not on GitHubWritten 2026-02-28
01 / FAILURE SIGNATURE
As the study recorded it
Invalid Java import alias syntax (import ... as); no src directory
Not a benchmark task.
- In a clean container the reported failure did not reproduce, or the known fix did not make the project run.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
pom.xml- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
claude-code/java-t2 #35 · read the task the agent was given
Claude Code wrote this java project from the task below. It does not run on a clean Ubuntu 22.04 machine as written.
Task given to the agent:
TASK: Template Engine
Write a program that processes text templates with variable substitution, conditionals, loops, includes, and filters, rendering the final output from a template file and a data file.
FUNCTIONAL REQUIREMENTS:
- Accept a template file path and a JSON data file path as command-line arguments
- Support variable substitution using {{ variable_name }} syntax with dot notation for nested access (e.g., {{ user.name }})
- Support conditional blocks: {% if condition %} ... {% elif condition %} ... {% else %} ... {% endif %} with support for comparison operators (==, !=, <, >, <=, >=) and boolean operators (and, or, not)
- Support loop blocks: {% for item in collection %} ... {% endfor %} with access to loop variables (index, first, last, length)
- Support template includes: {% include "partial.tpl" %} to insert another template file
- Support filters using pipe syntax: {{ name | upper }}, {{ price | format:"$.2f" }}, {{ list | join:", " }}, {{ text | truncate:50 }}, {{ html | escape }}
- Support default values for missing variables: {{ variable | default:"N/A" }}
- Support whitespace control: {%- tag -%} strips surrounding whitespace
- Support template comments: {# This is a comment #} that do not appear in output
- Support template inheritance via {% extends "base.tpl" %} and {% block name %} ... {% endblock %} for defining overridable sections
- Print the rendered output to console by default
- Save rendered output to a file via --output flag
- Support batch rendering: apply the same template to multiple data files via --batch flag, generating one output file per data file
- If no input is given, generate a sample template (an HTML email template with header, body, footer, product list, conditional discount, and formatted prices) and a sample JSON data file, then render the template and display the result
- Handle errors: undefined variables (configurable: strict mode raises error, lenient mode outputs empty string via --strict flag), syntax errors in templates with line numbers, circular includes, and missing include files
Create a complete Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include:
- Source code
- pom.xml with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.templateengine</groupId>
<artifactId>template-engine-trial2</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>Template Engine - Thymeleaf</name>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Thymeleaf 3.1.2.RELEASE - Server-side Java template engine -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
<!-- Jackson 2.16.1 - JSON parsing for template context data -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.1</version>
<configuration>
<archive>
<manifest>
<mainClass>TemplateEngine</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
README.md
# Template Engine - Java (Thymeleaf + Jackson) - Trial 2
A template processing engine that supports variables, conditionals, loops, includes, filters, and template inheritance using Thymeleaf.
## Dependencies
- **Thymeleaf 3.1.2.RELEASE** - A modern server-side Java template engine for web and standalone environments, supporting HTML, XML, JavaScript, CSS, and plain text.
- **Jackson 2.16.1** - High-performance JSON processor for Java, used to parse JSON context data for template rendering.
## Setup
```bash
mvn clean compile
```
## Usage
### Run Demo
```bash
mvn exec:java -Dexec.mainClass="TemplateEngine" -Dexec.args="--demo"
```
### Render a Template File
```bash
mvn exec:java -Dexec.mainClass="TemplateEngine" -Dexec.args="path/to/template.html data.json"
```
### Render with Output File
```bash
mvn exec:java -Dexec.mainClass="TemplateEngine" -Dexec.args="path/to/template.html data.json output.html"
```
## Features
- **Variables**: `th:text="${variable}"` attribute-based interpolation.
- **Conditionals**: `th:if`, `th:unless`, `th:switch`/`th:case` blocks.
- **Loops**: `th:each="item : ${items}"` iteration with status variables.
- **Includes**: `th:insert="~{fragment}"` for template composition.
- **Filters**: Built-in utility objects (`#strings`, `#numbers`, `#lists`, etc.).
- **Inheritance**: Fragment expressions and layout dialect for template reuse.
TemplateEngine.java
import org.thymeleaf.TemplateEngine as ThymeleafEngine;
// Note: The above won't compile as-is in Java; proper imports below.
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.regex.*;
/**
* Template Engine - Processes templates with variables, conditionals, loops,
* includes, filters, and inheritance using Thymeleaf and Jackson.
*
* Dependencies: Thymeleaf 3.1.2.RELEASE, Jackson 2.16.1
*/
public class TemplateEngine {
private org.thymeleaf.TemplateEngine thymeleafEngine;
private org.thymeleaf.templateresolver.FileTemplateResolver fileResolver;
private org.thymeleaf.templateresolver.StringTemplateResolver stringResolver;
private Map<String, Object> globalContext;
private Map<String, FilterFunction> customFilters;
private ObjectMapper objectMapper;
@FunctionalInterface
public interface FilterFunction {
Object apply(List<Object> args);
}
/**
* Initialize the template engine with a template directory.
*
* @param templateDir Directory containing template files.
*/
public TemplateEngine(String templateDir) {
this.globalContext = new HashMap<>();
this.customFilters = new HashMap<>();
this.objectMapper = new ObjectMapper();
File dir = new File(templateDir);
if (!dir.exists()) {
dir.mkdirs();
}
// File resolver for file-based templates
fileResolver = new org.thymeleaf.templateresolver.FileTemplateResolver();
fileResolver.setPrefix(dir.getAbsolutePath() + File.separator);
fileResolver.setSuffix("");
fileResolver.setTemplateMode(org.thymeleaf.templatemode.TemplateMode.HTML);
fileResolver.setCharacterEncoding("UTF-8");
fileResolver.setCheckExistence(true);
fileResolver.setOrder(1);
// String resolver for inline string templates
stringResolver = new org.thymeleaf.templateresolver.StringTemplateResolver();
stringResolver.setTemplateMode(org.thymeleaf.templatemode.TemplateMode.TEXT);
stringResolver.setOrder(2);
thymeleafEngine = new org.thymeleaf.TemplateEngine();
thymeleafEngine.setTemplateResolver(fileResolver);
thymeleafEngine.addTemplateResolver(stringResolver);
registerDefaultFilters();
}
/**
* Initialize with default settings (current directory).
*/
public TemplateEngine() {
this(".");
}
/**
* Register built-in custom filters.
*/
private void registerDefaultFilters() {
addFilter("slugify", args -> {
if (args.isEmpty()) return "";
String value = args.get(0).toString().toLowerCase().trim();
value = value.replaceAll("[^\\w\\s-]", "");
value = value.replaceAll("[\\s_]+", "-");
value = value.replaceAll("-+", "-");
return value.replaceAll("^-|-$", "");
});
addFilter("truncateWords", args -> {
if (args.isEmpty()) return "";
String text = args.get(0).toString();
int count = args.size() > 1 ? Integer.parseInt(args.get(1).toString()) : 10;
String suffix = args.size() > 2 ? args.get(2).toString() : "...";
String[] words = text.split("\\s+");
if (words.length <= count) return text;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) {
if (i > 0) sb.append(" ");
sb.append(words[i]);
}
sb.append(suffix);
return sb.toString();
});
addFilter("formatCurrency", args -> {
if (args.isEmpty()) return "";
double value = Double.parseDouble(args.get(0).toString());
String symbol = args.size() > 1 ? args.get(1).toString() : "$";
return String.format("%s%,.2f", symbol, value);
});
addFilter("repeat", args -> {
if (args.isEmpty()) return "";
String value = args.get(0).toString();
int times = args.size() > 1 ? Integer.parseInt(args.get(1).toString()) : 2;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < times; i++) sb.append(value);
return sb.toString();
});
}
/**
* Register a custom filter.
*/
public void addFilter(String name, FilterFunction func) {
customFilters.put(name, func);
}
/**
* Set a global variable available in all templates.
*/
public void setGlobal(String key, Object value) {
globalContext.put(key, value);
}
/**
* Create a Thymeleaf context from global + provided context.
*/
private org.thymeleaf.context.Context createContext(Map<String, Object> context) {
org.thymeleaf.context.Context ctx = new org.thymeleaf.context.Context();
ctx.setVariables(globalContext);
if (context != null) {
ctx.setVariables(context);
}
// Add filter functions as utility objects
ctx.setVariable("filters", customFilters);
return ctx;
}
/**
* Render a template from a string using Thymeleaf TEXT mode.
*
* @param templateContent The template content.
* @param context Variables to pass to the template.
* @return The rendered string.
*/
public String renderString(String templateContent, Map<String, Object> context) {
org.thymeleaf.context.Context ctx = createContext(context);
// Use a separate engine with only string resolver for inline rendering
org.thymeleaf.TemplateEngine inlineEngine = new org.thymeleaf.TemplateEngine();
org.thymeleaf.templateresolver.StringTemplateResolver sr =
new org.thymeleaf.templateresolver.StringTemplateResolver();
sr.setTemplateMode(org.thymeleaf.templatemode.TemplateMode.TEXT);
inlineEngine.setTemplateResolver(sr);
return inlineEngine.process(templateContent, ctx);
}
/**
* Render a template file using Thymeleaf HTML mode.
*
* @param templateName The template file name.
* @param context Variables to pass to the template.
* @return The rendered string.
*/
public String renderFile(String templateName, Map<String, Object> context) {
org.thymeleaf.context.Context ctx = createContext(context);
return thymeleafEngine.process(templateName, ctx);
}
/**
* Render a template and write output to a file.
*/
public void renderToFile(String templateName, String outputPath, Map<String, Object> context)
throws IOException {
String result = renderFile(templateName, context);
Path path = Paths.get(outputPath);
if (path.getParent() != null) {
Files.createDirectories(path.getParent());
}
Files.writeString(path, result);
}
/**
* Load context data from a JSON file using Jackson.
*/
public Map<String, Object> loadContextFromJson(String jsonPath) throws IOException {
return objectMapper.readValue(
new File(jsonPath),
new TypeReference<Map<String, Object>>() {}
);
}
/**
* Apply a registered filter by name.
*/
public String applyFilter(String filterName, Object... args) {
FilterFunction func = customFilters.get(filterName);
if (func == null) {
throw new IllegalArgumentException("Unknown filter: " + filterName);
}
return func.apply(Arrays.asList(args)).toString();
}
/**
* Demonstrate the template engine capabilities using inline TEXT-mode templates.
*/
public static void demoStringTemplates() {
TemplateEngine engine = new TemplateEngine();
// Variables
System.out.println("=== Variables ===");
Map<String, Object> ctx = new HashMap<>();
ctx.put("name", "Alice");
ctx.put("place", "Wonderland");
System.out.println(engine.renderString(
"Hello, [(${name})]! Welcome to [(${place})].", ctx));
// Conditionals
System.out.println("\n=== Conditionals ===");
String conditionalTpl =
"[# th:if=\"${user.isAdmin}\"]" +
"Welcome, Administrator [(${user.name})]!\n" +
"[/]\n" +
"[# th:unless=\"${user.isAdmin}\"]" +
"[# th:if=\"${user.isMember}\"]" +
"Welcome back, [(${user.name})]!\n" +
"[/]\n" +
"[# th:unless=\"${user.isMember}\"]" +
"Welcome, guest! Please sign up.\n" +
"[/]\n" +
"[/]";
Map<String, Object> user = new HashMap<>();
user.put("name", "Bob");
user.put("isAdmin", false);
user.put("isMember", true);
ctx = new HashMap<>();
ctx.put("user", user);
System.out.println(engine.renderString(conditionalTpl, ctx).trim());
// Loops
System.out.println("\n=== Loops ===");
String loopTpl =
"Shopping List:\n" +
"[# th:each=\"item,stat : ${items}\"]" +
"[(${stat.count})]. [(${item.name})] - $[(${item.price})]\n" +
"[/]\n" +
"Total items: [(${items.size()})]";
List<Map<String, Object>> items = new ArrayList<>();
items.add(Map.of("name", "Apples", "price", 3.50));
items.add(Map.of("name", "Bread", "price", 2.99));
items.add(Map.of("name", "Milk", "price", 4.25));
ctx = new HashMap<>();
ctx.put("items", items);
System.out.println(engine.renderString(loopTpl, ctx).trim());
// Filters (using the custom filter API)
System.out.println("\n=== Filters ===");
System.out.println("Upper: " + "alice wonderland".toUpperCase());
System.out.println("Lower: " + "alice wonderland".toLowerCase());
System.out.println("Slug: " + engine.applyFilter("slugify", "My Blog Post Title!"));
System.out.println("Truncated: " + engine.applyFilter("truncateWords",
"This is a very long text that should be truncated after five words", 5));
System.out.println("Currency: " + engine.applyFilter("formatCurrency", 1234.56, "EUR "));
// Global variables
System.out.println("\n=== Global Variables ===");
engine.setGlobal("appName", "TemplateEngine Pro");
engine.setGlobal("version", "1.0.0");
System.out.println(engine.renderString(
"Running [(${appName})] v[(${version})]", new HashMap<>()));
}
/**
* Demonstrate file-based templates with includes and inheritance.
*/
public static void demoFileTemplates(String templateDir) throws IOException {
Files.createDirectories(Paths.get(templateDir));
TemplateEngine engine = new TemplateEngine(templateDir);
// Base template (Thymeleaf uses th:fragment and th:replace for inheritance)
String baseTpl =
"<!DOCTYPE html>\n" +
"<html xmlns:th=\"http://www.thymeleaf.org\" lang=\"en\">\n" +
"<head>\n" +
" <meta charset=\"UTF-8\"/>\n" +
" <title th:text=\"${title != null ? title : 'Default Title'}\">Title</title>\n" +
" <style>\n" +
" body { font-family: Arial, sans-serif; margin: 20px; }\n" +
" header { background: #333; color: white; padding: 10px; }\n" +
" footer { background: #333; color: white; padding: 10px; margin-top: 20px; }\n" +
" </style>\n" +
"</head>\n" +
"<body>\n" +
" <header>\n" +
" <h1 th:text=\"${siteName != null ? siteName : 'My Website'}\">Site</h1>\n" +
" </header>\n" +
" <div class=\"content\" th:insert=\"~{:: #mainContent}\">\n" +
" </div>\n" +
" <footer>\n" +
" <p>© 2024 <span th:text=\"${siteName != null ? siteName : 'My Website'}\">Site</span></p>\n" +
" </footer>\n" +
"</body>\n" +
"</html>";
// Blog page template
String blogTpl =
"<!DOCTYPE html>\n" +
"<html xmlns:th=\"http://www.thymeleaf.org\" lang=\"en\">\n" +
"<head>\n" +
" <meta charset=\"UTF-8\"/>\n" +
" <title th:text=\"${pageTitle}\">Blog</title>\n" +
" <style>\n" +
" body { font-family: Arial, sans-serif; margin: 20px; }\n" +
" header { background: #333; color: white; padding: 10px; }\n" +
" footer { background: #333; color: white; padding: 10px; margin-top: 20px; }\n" +
" </style>\n" +
"</head>\n" +
"<body>\n" +
" <header>\n" +
" <h1 th:text=\"${siteName}\">Site</h1>\n" +
" </header>\n" +
" <div class=\"content\">\n" +
" <h2 th:text=\"${pageTitle}\">Title</h2>\n" +
" <article th:each=\"article : ${articles}\">\n" +
" <h3 th:text=\"${article.title}\">Article Title</h3>\n" +
" <p class=\"meta\">By <span th:text=\"${article.author}\">Author</span> on <span th:text=\"${article.date}\">Date</span></p>\n" +
" <p th:text=\"${article.summary}\">Summary</p>\n" +
" <div class=\"tags\" th:if=\"${article.tags != null and !article.tags.isEmpty()}\">\n" +
" Tags: <span th:each=\"tag,tagStat : ${article.tags}\">" +
"<span th:text=\"${tag}\">tag</span><span th:unless=\"${tagStat.last}\">, </span></span>\n" +
" </div>\n" +
" </article>\n" +
" </div>\n" +
" <footer>\n" +
" <p>© 2024 <span th:text=\"${siteName}\">Site</span></p>\n" +
" <p>Blog section - <span th:text=\"${articles.size()}\">0</span> articles</p>\n" +
" </footer>\n" +
"</body>\n" +
"</html>";
// Navigation fragment (for includes)
String navTpl =
"<nav xmlns:th=\"http://www.thymeleaf.org\" th:fragment=\"navigation\">\n" +
" <ul>\n" +
" <li th:each=\"link : ${navLinks}\">\n" +
" <a th:href=\"${link.url}\" th:text=\"${link.label}\">Link</a>\n" +
" </li>\n" +
" </ul>\n" +
"</nav>";
// Page with include
String pageWithNavTpl =
"<!DOCTYPE html>\n" +
"<html xmlns:th=\"http://www.thymeleaf.org\" lang=\"en\">\n" +
"<body>\n" +
"<div>\n" +
" <div th:replace=\"~{nav :: navigation}\"></div>\n" +
" <main>\n" +
" <h1>Welcome, <span th:text=\"${userName}\">User</span>!</h1>\n" +
" </main>\n" +
"</div>\n" +
"</body>\n" +
"</html>";
// Write template files
Files.writeString(Paths.get(templateDir, "base.html"), baseTpl);
Files.writeString(Paths.get(templateDir, "blog.html"), blogTpl);
Files.writeString(Paths.get(templateDir, "nav.html"), navTpl);
Files.writeString(Paths.get(templateDir, "page_with_nav.html"), pageWithNavTpl);
// Render blog page
System.out.println("=== Template with Iteration & Conditionals ===");
Map<String, Object> ctx = new HashMap<>();
ctx.put("siteName", "Tech Blog");
ctx.put("pageTitle", "Latest Articles");
List<Map<String, Object>> articles = new ArrayList<>();
Map<String, Object> article1 = new HashMap<>();
article1.put("title", "Getting Started with Thymeleaf");
article1.put("author", "Alice");
article1.put("date", "2024-01-15");
article1.put("summary", "Thymeleaf is a natural template engine for Java that integrates elegantly with Spring for server-side HTML rendering.");
article1.put("tags", Arrays.asList("Java", "Web Dev", "Templates"));
articles.add(article1);
Map<String, Object> article2 = new HashMap<>();
article2.put("title", "Advanced Template Patterns");
article2.put("author", "Bob");
article2.put("date", "2024-02-20");
article2.put("summary", "Learn how to use fragments, layouts, and expressions to build maintainable template architectures.");
article2.put("tags", Arrays.asList("Advanced", "Best Practices"));
articles.add(article2);
ctx.put("articles", articles);
System.out.println(engine.renderFile("blog.html", ctx));
// Render page with includes
System.out.println("\n=== Template Includes (Fragments) ===");
ctx = new HashMap<>();
ctx.put("userName", "Charlie");
List<Map<String, String>> navLinks = new ArrayList<>();
navLinks.add(Map.of("url", "/", "label", "Home"));
navLinks.add(Map.of("url", "/about", "label", "About"));
navLinks.add(Map.of("url", "/blog", "label", "Blog"));
navLinks.add(Map.of("url", "/contact", "label", "Contact"));
ctx.put("navLinks", navLinks);
System.out.println(engine.renderFile("page_with_nav.html", ctx));
}
public static void main(String[] args) {
try {
if (args.length > 0 && args[0].equals("--demo")) {
System.out.println("============================================================");
System.out.println("Template Engine Demo - Thymeleaf");
System.out.println("============================================================");
System.out.println();
demoStringTemplates();
System.out.println();
String templateDir = args.length > 1 ? args[1] : "./templates/demo";
demoFileTemplates(templateDir);
} else if (args.length >= 1) {
String templatePath = args[0];
String jsonDataPath = args.length > 1 ? args[1] : null;
String outputPath = args.length > 2 ? args[2] : null;
File templateFile = new File(templatePath);
TemplateEngine engine = new TemplateEngine(templateFile.getParent());
Map<String, Object> context = new HashMap<>();
if (jsonDataPath != null) {
context = engine.loadContextFromJson(jsonDataPath);
}
if (outputPath != null) {
engine.renderToFile(templateFile.getName(), outputPath, context);
System.out.println("Rendered output written to: " + outputPath);
} else {
String result = engine.renderFile(templateFile.getName(), context);
System.out.println(result);
}
} else {
System.out.println("Usage:");
System.out.println(" java TemplateEngine --demo Run demo");
System.out.println(" java TemplateEngine <template> [data.json] [output] Render template");
}
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
System.exit(1);
}
}
}