はじめに
Javaのサーバーサイド開発で「テンプレートエンジン」と聞くと、ThymeleafやJSPを思い浮かべる方が多いのではないでしょうか。
しかし、日本国内のEC基盤やBtoB系Webシステムでは、2000年代中盤から使われ続けている「Mayaa」というテンプレートエンジンが今でも現役で活躍しています。
この記事では、実際の業務プロジェクトで得た知見をもとに、Mayaaの基本的な仕組みから実践的なテクニックまでを初心者向けに解説します。
レガシーシステムの保守を任された方や、Mayaaを使うプロジェクトに配属された方の参考になれば幸いです。
1. Mayaaとは何か
Mayaaは、Seasarプロジェクトから生まれたサーバーサイドWebテンプレートエンジンです。
正式な説明としては「HTMLベースのテンプレートによるプログラマとデザイナの作業分担を強く意識したWebフロントサービスエンジン」となります。
最新の安定版は2026年2月にリリースされたバージョン1.3.0で、動作要件はJava 8以上、Servlet API 2.4から4.0に対応しています。
Apache License 2.0のもとで公開されており、ソースコードは GitHub (seasarorg/mayaa) で管理されています。
2006年頃の初期リリースから約20年が経過した今でもメンテナンスが続いていることは注目に値します。
2. Mayaaの最大の特徴 : HTMLとロジックの完全分離
Mayaaが他のテンプレートエンジンと決定的に異なるのは、HTMLファイルそのものには一切のロジックを書かないという設計思想です。
通常のテンプレートエンジンとの違い
JSPやThymeleafでは、HTML(またはそれに近いファイル)の中にロジックや式を埋め込みます。
JSPやThymeleafでは、HTML(またはそれに近いファイル)の中にロジックや式を埋め込みます。
<!-- Thymeleafの例 -->
<span th:text="${user.name}">ユーザー名</span>
一方、Mayaaでは2つのファイルに分けて管理します。
HTMLテンプレート (.xhtml)
<span m:id="CUSTOMER_NAME_OUTPUT">山田太郎</span>
Mayaa定義ファイル (.mayaa)
<?xml version="1.0" encoding="UTF-8"?>
<m:mayaa xmlns:m="http://mayaa.seasar.org">
<e:write m:id='CUSTOMER_NAME_OUTPUT' value='${bean.getCustomerFullName()}' />
</m:mayaa>
HTMLテンプレート側には m:id 属性を付けるだけで、出力する値や条件分岐のロジックはすべて .mayaa ファイルに記述します。
この設計のメリット
- デザイナーはHTMLファイルをそのままブラウザで開いてデザインを確認できる
- プログラマはロジックを
.mayaaファイルに集約でき、HTMLの構造を壊さない - HTMLテンプレートが「有効なXHTML」のままであるため、デザインツールとの互換性が保たれる
3. Mayaaの基本的なファイル構成
Mayaaプロジェクトでは、1つの画面に対して通常2つ(場合によっては3つ以上)のファイルが存在します。
app/
├── view/
│ └── front/
│ ├── top.xhtml ← HTMLテンプレート
│ └── top.mayaa ← ロジック定義ファイル
└── WEB/
└── web.xml ← Mayaaサーブレットフィルタの設定
XHTMLテンプレートのルール
- 拡張子は
.xhtmlで、XHTML 1.0 Strict準拠が基本です - Mayaaの名前空間として
xmlns:m="http://mayaa.seasar.org"をhtml要素に宣言します - 動的に差し替えたい箇所に
m:id属性を付与します
.mayaaファイルのルール
- 対応するXHTMLと同じディレクトリに、同じベース名で配置する
- XMLフォーマットで記述し、ルート要素は
<m:mayaa> -
m:idを使ってXHTML側の要素とバインドし、出力値や条件を定義
4. よく使う5つの基本タグ
Mayaaの .mayaa ファイルで頻繁に使われるタグを紹介します。
e:write(値の出力)
指定した式の結果をHTML上に出力します。
<!-- テキスト出力 -->
<e:write m:id='POSTED_DATE_OUTPUT' value='${bean.getFormattedPostDate()}' escape='false' />
escape="false" を指定するとHTMLエスケープを行いません。ユーザー入力を含む場合は必ずエスケープ処理を行ってから出力してください。
e:if(条件分岐)
条件を満たすときだけ、対応するHTML要素を出力します。
<!-- 投稿日が設定されている場合のみ表示 -->
<e:if m:id='IF_HAS_POST_DATE' test='${!"".equals(bean.getRowValue("POST_DATE"))}' />
属性値の囲みにシングルクォート ' を使うことで、内部のダブルクォート " をエスケープせずに書けます。プロジェクト全体でこの記法が統一されています。
e:for(ループ)
条件が真である間、対応するHTML要素を繰り返し出力します。
<!-- 記事一覧をループ -->
<e:for m:id='LOOP_ARTICLE_LIST' test='${bean.nextRow()}' />
m:echo(属性の書き換え)
既存のHTML要素の属性値を動的に差し替えます。リンクのhref属性を書き換える場面でよく使います。
<!-- リンク先URLを動的に設定 -->
<m:echo m:id='ARTICLE_ANCHOR_TAG'>
<m:attribute name='href' value='${bean.buildDetailPageUrl(bean.getRowValue("ARTICLE_ID"))}' escape='false' />
</m:echo>
m:insert(テンプレートのインクルード)
他のテンプレートファイルを差し込みます。ヘッダーやフッターなどの共通部品に使います。
<m:insert path='/WEB-INF/mayaa/shared_scripts.xhtml' />
5. XHTMLテンプレート側の書き方
実際のプロジェクトでのXHTMLテンプレートを見てみましょう。トップページを例に取ります。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:m="http://mayaa.seasar.org"
xml:lang="ja"
lang="ja"
>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>トップページ</title>
</head>
<body>
<!-- ヘッダーのインクルード -->
<iframe
m:id="INCLUDE_HEADER"
src="common/header.xhtml"
frameborder="0"
scrolling="no"
width="100%"
></iframe>
<!-- メインコンテンツ -->
<div id="main">
<h2>新着情報</h2>
<div class="article-list">
<div class="article-item" m:id="LOOP_ARTICLE_LIST">
<span m:id="IF_HAS_POST_DATE">
<span m:id="POSTED_DATE_OUTPUT">2025/04/01</span>
</span>
<a m:id="ARTICLE_ANCHOR_TAG" href="#">
<span m:id="ARTICLE_TITLE_OUTPUT">記事タイトル</span>
</a>
</div>
</div>
<!-- バナーエリア -->
<div m:id="IF_HAS_CAMPAIGN" class="campaign-area">
<div m:id="LOOP_CAMPAIGN_LIST">
<a m:id="CAMPAIGN_ANCHOR_TAG" href="#">
<img
m:id="CAMPAIGN_IMAGE_TAG"
src="images/placeholder.png"
alt="キャンペーン"
/>
</a>
</div>
</div>
</div>
<!-- フッターのインクルード -->
<iframe
m:id="INCLUDE_FOOTER"
src="common/footer.xhtml"
frameborder="0"
scrolling="no"
width="100%"
></iframe>
</body>
</html>
ポイント
-
m:idに書かれたダミーテキスト(「2025/04/01」「記事タイトル」など)は、ブラウザでHTMLとして直接開いたときのプレビュー用です - 実際のサーバー実行時には
.mayaaファイルの定義に従って動的な値に差し替わります - コメントで各セクションの役割を明記しておくと、チームでの保守性が向上します
6. m:idの命名規則と名前空間の考え方
Mayaaプロジェクトの規模が大きくなると、m:idの管理が重要になります。
命名規則の基本パターン
| パターン | 意味 | 例 |
|---|---|---|
common.XXX |
全画面共通のm:id定義 | common.INCLUDE_HEADER |
XXX_OUTPUT |
値を出力する箇所 | POSTED_DATE_OUTPUT |
IF_XXX |
条件分岐 | IF_HAS_CAMPAIGN |
LOOP_XXX |
繰り返し | LOOP_ARTICLE_LIST |
XXX_TAG |
要素や属性の書き換え | ARTICLE_ANCHOR_TAG |
7. beforeRenderとビジネスロジックの接続
.mayaa ファイルの中で最も重要な部分が <m:beforeRender> です。ここでビジネスロジック(Java側のBean)との接続を行います。
<?xml version="1.0" encoding="UTF-8"?>
<m:mayaa xmlns:m="http://mayaa.seasar.org"
xmlns:e="http://example.com/engine"
m:templateSuffix="${bean.resolveDeviceSuffix()}">
<m:beforeRender>
var bean = getBusinessBean(
"com.example.myapp.web.front.TopPageAction"
);
var currentPage = "top.html";
var queryString = RequestUtil.buildQueryString(bean.getSearchParams(), "UTF-8");
</m:beforeRender>
<!-- 以下、m:id定義が続く -->
</m:mayaa>
beforeRenderの役割
- 画面描画前に実行されるJavaScriptブロック(Rhinoエンジンで実行)です
- ビジネスロジック層のJava Beanを取得し、テンプレート内で使える変数にバインドします
- ページ固有の初期化処理(パラメータの取得やURLの組み立てなど)を行います
注意点
- ここに複雑なロジックを書くことは推奨されません
- あくまでJava側のメソッドを呼び出すための「橋渡し」として使います
- 重い処理はJava側のBeanに実装し、mayaaからは呼び出すだけにしてください
8. JSPやThymeleafと比較したMayaaのポジション
最後に、Mayaaが他のテンプレートエンジンとどう違うのか整理しておきます。
HTMLの純粋性
| エンジン | HTMLとしてブラウザ表示 | ロジックの配置場所 |
|---|---|---|
| JSP | 不可(<% %> タグ等が含まれる) |
HTML内に混在 |
| Thymeleaf | 可能(th: 属性はブラウザに無視される) |
HTML内に属性として記述 |
| Mayaa | 可能(m:id 属性のみ) |
別ファイル(.mayaa)に完全分離 |
開発スタイルの違い
- JSP は「プログラマが全部書く」スタイルに向いています
- Thymeleaf は「プログラマもデザイナーも同じファイルを編集する」スタイルです
- Mayaa は「デザイナーがHTMLを、プログラマがmayaaを、完全に別々に管理する」スタイルです
Mayaaが現在も使われ続ける理由
Mayaaは新規プロジェクトで採用されることは少なくなりました。しかし、以下の理由から現役で運用されているプロジェクトが多く存在します。
- HTML側にロジックが一切入らないため、デザインの刷新が容易
- テンプレートサフィックスの仕組みにより、同じ画面のPC版・スマートフォン版を柔軟に切り替えられる
- 既存の大規模Webシステムで長年安定稼働しており、移行コストが高いのが実情
Mayaaを扱う上での落とし穴と対策
ここまでの基本を踏まえた上で、実務で遭遇しやすい問題とその回避策をまとめます。
XPathの使用は避ける
Mayaaでは m:xpath 属性でHTML要素を指定する方法も存在しますが、パフォーマンスの問題とテンプレート構造変更時の影響が大きいため、使用しないことを推奨します。必ず m:id を使ってバインドしてください。
.mayaaファイルにロジックを書きすぎない
<m:beforeRender> 内のスクリプトや、value 属性の式が複雑になりがちです。長い条件式や計算処理はJava側のメソッドに切り出して、mayaaからは1行で呼べるようにしましょう。
<!-- 悪い例:mayaa内で複雑な計算ロジック -->
<e:write m:id='TOTAL_AMOUNT_OUTPUT'
value='${CalcHelper.addDecimal(bean.getSubtotal(), bean.getTaxAmount())}' />
<!-- 良い例:Java側でメソッド化して呼ぶだけ -->
<e:write m:id='TOTAL_AMOUNT_OUTPUT' value='${bean.getDisplayTotalAmount()}' />
テンプレートのキャッシュ問題
Mayaaはテンプレートをパースした結果をキャッシュします。開発中にテンプレートを修正しても反映されない場合は、アプリケーションサーバーの再起動、またはMayaaのキャッシュディレクトリ(.mayaaSpecCache)のクリアが必要です。
scriptCacheの上限に注意
GitHub上のIssueにも報告されていますが、同時リクエストが多い環境で内部のスクリプトキャッシュに問題が発生するケースがあります。バージョン1.3.0で改善されている部分もありますが、負荷の高いシステムでは設定の見直しを検討してください。
まとめ
Mayaaは「HTMLの純粋性を守る」という思想で設計された、日本発のユニークなテンプレートエンジンです。新規採用は減少傾向にありますが、既存の大規模Webシステムで確実に動き続けている技術です。
レガシーシステムの保守は「古い=悪い」ではありません。設計思想を正しく理解し、そのルールに沿って開発を進めることが、安定したサービス運用につながります。
この記事がMayaaプロジェクトに初めて関わる方の一助となれば幸いです。