エキサイト株式会社メディアプラットフォーム事業部エンジニア佐々木です。SpringBootのデファクトのテンプレートエンジンはThymeleafだと思いますが、JTEというテンプレートエンジンもここ最近Spring Initilizrで選べるようになりました。 プリコンパイルできることなどで、Thymeleafより10倍以上パフォーマンスがでるようです。トラフィックが多いサービスを多く抱えているので、試してみます。
ディレクトリ構成
├── build.gradle.kts
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
├── main
├── java
│ └── net
├── jte
│ ├── .jteroot
│ ├── index.jte
│ └── layout.jte
└── resources
├── application.properties
build.gradleの設定
依存関係は、Spring Initirizrで選択したものをそのまま使用しています。
plugins {
id("gg.jte.gradle") version "3.1.16"
}
dependencies {
// SpringBootの開発用の標準的な設定
implementation("org.springframework.boot:spring-boot-starter-web")
developmentOnly("org.springframework.boot:spring-boot-devtools")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")}
// JTEの依存関係
implementation("gg.jte:jte:3.1.16")
implementation("gg.jte:jte-spring-boot-starter-3:3.1.16")
application.properties
application.propertiesは下記のような設定をします。
spring.application.name=jte # 開発モードで、ホットリロードを行います gg.jte.development-mode=true # テンプレートの拡張子を決めます gg.jte.template-suffix=.jte # コンパイルして高速するためのオプション(開発時はfalseでもいい) gg.jte.precompile=false
Javaコード部分
説明のために1ファイルに押し込んでいます。Javaコードの部分はThymeleafのときと同じです。
@SpringBootApplication public class JteApplication { public static void main(String[] args) { SpringApplication.run(JteApplication.class, args); } @Controller @RequestMapping("") public static class RootController { @RequestMapping("") public String index(Model model) { Info info = new Info("Hello, JTE!", 1, List.of("a", "b", "c")); model.addAttribute("info", info); return "index"; } } public record Info(String message, int id, List<String> alphabet) { } }
テンプレート
JTEテンプレートは、HTMLの中にJavaコードを記載できます。
index.jte <%-- インポートするクラスを書きます --%> @import jp.co.excite.sample.jte.JteApplication <%-- テンプレートに渡されるクラス書きます --%> @param JteApplication.Info info <h1>hello world3</h1> info: ${info.message()} @if(info.id() < 2) <h2>id: ${info.id()}</h2> @endif <ul> @for(String item : info.alphabet()) <li>have alphabet item: ${item}</li> @endfor </ul>
上記のコードで下記のような出力になります。

レイアウトファイル
レイアウトファイルも備えています。
レイアウトファイルの作成
サンプルのレイアウトファイルは下記のようになります。
layout.jte
@import gg.jte.Content
<%-- レイアウトファイルに渡される引数を列挙します --%>
@param String title
@param String h2
@param Content content
<head>
<title>${title}</title>
</head>
<body>
<h1> ${h2} </h1>
<div class="content">
${content}
</div>
</body>
@param のところが異なります。index.jteファイルの時とは異なり、@param {型} 変数名 という形になっています。この場合は、このファイルに渡される引数の意味になります。
レイアウトファイルを使用する
先ほどのHTMLを出力したファイルにレイアウトファイルを適用しようと思います。
@template.layout(...) の部分がレイアウトファイルの読み込みになります。
@import jp.co.excite.sample.jte.JteApplication
@param JteApplication.Info info
@template.layout(title = "aaaa"
, h2 = "hello world3"
, content = @`
<h2>高速なパフォーマンス</h2>
<p>JTEはテンプレートを事前コンパイルしてJavaコードに変換するため、実行時の解析オーバーヘッドがなく、非常に高速に動作します。ThymeleafやFreemarkerのようなランタイム解析型のエンジンと比べてレスポンス時間が短く、特に高負荷環境で優れたパフォーマンスを発揮します。</p>
<h2>Javaとのシームレスな統合</h2>
<p>JTEはJavaの型システムを活用し、テンプレート内で使用する変数やメソッドの型安全性が確保されます。IDEでの補完やリファクタリングもサポートされており、Javaコードとテンプレートを効率的に開発できます。これはHTML属性ベースのエンジンにはない利点です。</p>
<h2>シンプルで直感的な構文</h2>
<p>JTEの構文はシンプルでHTMLに近く、学習が容易です。制御構文は必要最小限に抑えられ、@templateや@Content型で柔軟に再利用可能なテンプレートを構築できます。ロジックをJava側に分離できるため、テンプレートが複雑化しにくいのも特徴です。</p>
`)
<h1>Hello world!</h1>
info: ${info.message()}
@if(info.id() < 2)
<h2>id: ${info.id()}</h2>
@endif
<ul>
@for(String item : info.alphabet())
<li>have alphabet item: ${item}</li>
@endfor
</ul>
基本的にはJavaで使用できる型をそのまま使えますが、テキストブロックでHTMLを書きたい場合があると思いますので、Contentというクラスが用意されいて、@``構文で中に記述されたHTMLが展開されるような仕組みになっています。上記の例でも使用しています。出力は下記になります。

まとめ
JTEを使用してみましたが、機能数もすくなく、Javaがそのまま使えましたので濫用は禁物ですが便利だと思います。ホットリロードが標準装備されている点は開発体験が尊重されていて、いいとおもいました。シンプルなレイアウト機能もAlpine.jsやhtmx.jsと組み合わせやすいと個人的に感じております。なによりパフォーマンスがいいので、弊社のような高トラフィックなサイトと相性はよさそうです。段階的に実験していこうと思います。