JSF(JavaServer Faces)は、Javaのサーバーサイドで画面を組み立てるWebフレームワークで、Jakarta EEの標準仕様の1つです。Jakarta EEへの移管後はJakarta Server Facesと呼ばれ、4.0でJakarta Facesへ改称されました。2020年のJakarta EE 9に対応する3.0では、パッケージが javax.faces から jakarta.faces へ移りました。この記事では、JSFの仕組みとJSPとの違い、最新バージョン、そして2026年10月時点の最新実装(Mojarra 4.1.18)をTomcat 11で実際に動かして確かめた挙動を、コードとログ付きで解説します。
まとめ:JSFの要点と最新バージョン
- JSFは画面をUIコンポーネントの木としてサーバー側で保持し、リクエストを6フェーズのライフサイクルで処理するMVCフレームワーク。
- 最新の仕様はJakarta Faces 4.1(Jakarta EE 11・仕様承認2024年6月4日)。実装はMojarra 4.1.18とMyFaces 4.1.4。Faces 5.0はJakarta EE 12向けに策定中。
- ビューはFacelets(.xhtml)。JSPのサポートとネイティブの
@ManagedBeanはFaces 4.0で削除され、ビーンはCDIの@Namedで書く。 - Tomcat 11ではMojarraとWeld(CDI実装)の2つを足せば動き、web.xmlが無くても
*.xhtml・*.jsf・*.faces・/faces/*に自動でマッピングされた(実測)。 - 入力検証に失敗するとモデル更新とアクションは実行されない。ViewStateを偽造したPOSTは
ViewExpiredExceptionで止まった(実測)。 - 新規開発で第一候補になる場面は少ない。既存のJakarta EE業務システムの保守と、Javaだけで完結させたい管理画面で選ぶ技術。
JSFとは:JavaServer FacesからJakarta Facesへの改称
JSFは「JavaServer Faces」の略です。JSR 127として2004年3月11日に1.0が最終リリースされ、Java EEの標準UIフレームワークになりました。ボタン・入力欄・表といった部品をUIコンポーネントとして宣言し、サーバー上のコンポーネントツリーで入力値の受け取り・検証・再描画を行います。MVCに当てはめると、Viewが画面定義(Facelets)、Controllerが FacesServlet、Modelがビーンです。MVCの考え方そのものはMVCとは?MVVM・MVPとの違いを同じ機能のコードで比較で比べています。
Java EEがOracleからEclipse Foundationへ移管されてJakarta EEになると、仕様名は3.0で「Jakarta Server Faces」、4.0から「Jakarta Faces」に変わりました。現場でも検索でも「JSF」の略称は今も通じます。なお「JSF」は日本スケート連盟(Japan Skating Federation)などの略称でもあり、この記事が扱うのはJavaのフレームワークです。
ブラウザのアドレス欄に出る「xxx.jsf」は、多くの場合ファイルではなくURLの拡張子です。FacesServlet が *.jsf を受け付けているためで、実体のテンプレートは .xhtml ファイルです。
javax.facesからjakarta.facesへの名前空間変更
Faces 3.0(Jakarta EE 9・仕様承認2020年10月21日)で、パッケージが javax.faces.* から jakarta.faces.* に変わりました。両者にバイナリ互換はないため、Jakarta EE 9以降へ移すときはJavaソース・設定ファイル・PrimeFacesなどのライブラリを一斉に jakarta 版へ揃えます。JDKの更新とあわせた移行の段取りはJavaマイグレーションの進め方|JDK 8→21/25の手順とSpring Boot・Jakarta移行にまとめています。
画面側のタグの名前空間も、Faces 4.0から jakarta.faces.html のような短いURIが使えます。策定中のFaces 5.0には「古いXML名前空間のサポート削除(#2105)」が予定項目に挙がっているので、http://xmlns.jcp.org/jsf/html のままの画面は今のうちに置き換えておくのが安全です。
JSFの最新バージョンと実装(Mojarra・MyFaces)
JSFの「最新バージョン」には、仕様の版と実装の版の2つがあります。仕様の最新はJakarta Faces 4.1で、Jakarta EE 11のWeb Profileに含まれます。4.1はSecurityManagerへの参照削除とCDIとの整合が中心の小改訂で、4.0から書き方はほぼ変わりません。
| 仕様 | 確定日 | 対応プラットフォーム | 主な変化 |
|---|---|---|---|
| JSF 1.0(JSR 127) | 2004-03-11 | 単体仕様 | 初版。ビューはJSP |
| JSF 2.0(JSR 314) | 2009-07-01 | Java EE 6 | Facelets標準化・Ajax |
| JSF 2.3(JSR 372) | 2017-04-17 | Java EE 8 | CDI連携の強化 |
| Jakarta Server Faces 3.0 | 2020-10-21 | Jakarta EE 9 | javax→jakarta |
| Jakarta Faces 4.0 | 2022-05-18 | Jakarta EE 10 | JSP・@ManagedBean削除 |
| Jakarta Faces 4.1 | 2024-06-04 | Jakarta EE 11 | SecurityManager参照削除 |
| Jakarta Faces 5.0 | 策定中 | Jakarta EE 12 | 非推奨APIと旧名前空間の削除予定 |
確定日はJSFがJCPの最終リリース日、3.0以降が仕様委員会の承認日です。Faces 5.0はEclipseの計画では2026年2月28日予定でしたが、2026年10月14日時点の公式ページは「Under development」のままで、APIはMaven Centralに 5.0.0-M10 までのマイルストーン版だけが出ています(maven-metadata.xml の最終更新は2026年10月9日)。
実際に動かすのは実装ライブラリです。Eclipse FoundationのMojarraと、ApacheのMyFacesの2系統があり、Maven Centralの最新安定版はMojarraが org.glassfish:jakarta.faces:4.1.18(2026年10月9日更新)、MyFacesが 4.1.4 です。WildFly・Payara・GlassFishなどのアプリケーションサーバーには実装が同梱されているため、自分で依存に加えるのはTomcatやJettyのようなサーブレットコンテナで動かす場合です。
JSFとJSPの違い
JSP(JavaServer Pages)はHTMLの中にJavaのコードやタグを書いて出力する表示技術です。JSFはコンポーネントとイベントで画面を扱うフレームワークで、表示はFaceletsに任せます。比べる階層が違うので、「どちらが新しいか」よりも「JSFはJSPを表示部品として使うのをやめた」と理解すると整理がつきます。
| 観点 | JSP | JSF(Facelets) |
|---|---|---|
| 位置づけ | 表示技術 | MVCフレームワーク |
| 画面の単位 | ページ単位のスクリプト | コンポーネントツリー |
| 画面の状態 | 自前で管理 | ViewStateで自動保持 |
| 入力検証 | 自前で実装 | 標準バリデータ・Bean Validation |
| ファイル | .jsp | .xhtml |
JSF 1.xではビューにJSPを使っていましたが、2.0でFaceletsが標準になり、Faces 4.0の変更点には「Remove all JSP support(#1546)」が明記されています。Faces 4.0以降のJSFでJSPの画面は書けません。
Tomcat 11でJakarta Faces 4.1を動かす最小構成
アプリケーションサーバーを使わずに、Tomcat 11.0.27(JDK 25)上で最小のアプリを組みました。以降のログと挙動はすべてこの構成で確認したものです。
pom.xmlの実行時依存とコンパイル用Servlet API
Tomcatはサーブレットコンテナなので、Faces実装とCDI実装を自分で入れます。MojarraのREADMEはTomcatで使うCDI実装としてWeld 6.0系を挙げています。2026年9月にWeld 7.0.0.Finalも出ていますが、こちらはCDI 5.0(Jakarta EE 12)向けで、Faces 4.1と組むのは6.0系です。
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.faces</artifactId>
<version>4.1.18</version>
</dependency>
<dependency>
<groupId>org.jboss.weld.servlet</groupId>
<artifactId>weld-servlet-core</artifactId>
<version>6.0.4.Final</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
<scope>provided</scope>
</dependency>
Weldはリスナーを書かなくても、起動時に「WELD-ENV-001008: Initialize Weld using ServletContainerInitializer」と出して自動で立ち上がりました。続いて「Mojarra 4.1.18 を初期化します」が出れば準備完了です。
beans.xml・faces-config.xmlと画面・ビーン
WEB-INF に置く設定は2つです。beans.xml はCDIのビーン探索を有効にし、faces-config.xml は空でもFacesを有効にする目印になります。
<!-- WEB-INF/beans.xml -->
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee" version="4.1" bean-discovery-mode="annotated"/>
<!-- WEB-INF/faces-config.xml -->
<faces-config xmlns="https://jakarta.ee/xml/ns/jakartaee" version="4.1">
</faces-config>
画面(index.xhtml)は、3文字以上の名前を必須入力にしたフォームです。
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html" xmlns:f="jakarta.faces.core">
<h:head><title>demo</title></h:head>
<h:body>
<h:form id="f">
<h:inputText id="name" value="#{userBean.name}" required="true">
<f:validateLength minimum="3"/>
</h:inputText>
<h:commandButton id="save" value="save" action="#{userBean.save}"/>
<h:messages id="msgs"/>
</h:form>
</h:body>
</html>
ビーンはCDIの @Named とスコープ注釈で登録します。save() の戻り値 "result" が、そのまま遷移先の result.xhtml を指します(暗黙的ナビゲーション)。
import jakarta.inject.Named;
import jakarta.enterprise.context.RequestScoped;
@Named
@RequestScoped
public class UserBean {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String save() {
return "result";
}
}
遷移先の result.xhtml は、保存した名前を表示するだけの画面です。
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="jakarta.faces.html">
<h:body>saved: #{userBean.name}</h:body>
</html>
ファイルの置き場所と、ビルドから配置までの手順は次のとおりです。pom.xmlは <packaging>war</packaging> にして、上の3つの依存を書きます。
pom.xml
src/main/java/demo/UserBean.java
src/main/webapp/index.xhtml
src/main/webapp/result.xhtml
src/main/webapp/WEB-INF/beans.xml
src/main/webapp/WEB-INF/faces-config.xml
$ mvn package
$ cp target/demo.war apache-tomcat-11.0.27/webapps/
$ apache-tomcat-11.0.27/bin/catalina.sh start
# ブラウザで http://localhost:8080/demo/index.xhtml を開く
WARの名前は <build><finalName>demo</finalName></build> で demo.war にしています。3文字以上を入れて送信すると result.xhtml に「saved: 入力した名前」が表示されます。
FacesServletの自動マッピングと4つのURLパターン
web.xmlを置かずに配置しても、/demo/index.xhtml・/demo/index.jsf・/demo/index.faces・/demo/faces/index.xhtml の4つがすべてHTTP 200で同じ画面を返しました。検索で「jsf ファイル」と調べて見つかる .jsf のURLは、この自動マッピングで生まれるものです。公開URLを1つに揃えたいときは、web.xmlで FacesServlet のマッピングを明示して、ほかのパターンを使わないようにします。
ライフサイクル6フェーズと検証成否による実行順序
JSFは1回のリクエストを、決まった順の6フェーズで処理します。フォーム送信のどの時点で値が検証され、いつビーンに入るのかは、この順番で決まります。
| 順 | フェーズ | 役割 |
|---|---|---|
| 1 | Restore View | コンポーネントツリーを復元 |
| 2 | Apply Request Values | 送信値を各部品に取り込む |
| 3 | Process Validations | 変換と入力検証 |
| 4 | Update Model Values | 検証済みの値をビーンへ反映 |
| 5 | Invoke Application | アクションメソッドを実行 |
| 6 | Render Response | HTMLを描画しViewStateを保存 |
各フェーズの開始をログに出す PhaseListener を faces-config.xml に登録し、ビーンのsetterと save() にも System.out.println でログを入れて、3通りのリクエストを送りました。
public class LogPhaseListener implements PhaseListener {
public PhaseId getPhaseId() { return PhaseId.ANY_PHASE; }
public void beforePhase(PhaseEvent e) {
System.out.println("[phase] " + e.getPhaseId());
}
}
<!-- faces-config.xml に追記 -->
<lifecycle>
<phase-listener>demo.LogPhaseListener</phase-listener>
</lifecycle>
#### 初回表示(GET)
[phase] RESTORE_VIEW 1
[phase] RENDER_RESPONSE 6
#### 2文字「ab」で送信(検証エラー)
[phase] RESTORE_VIEW 1
[phase] APPLY_REQUEST_VALUES 2
[phase] PROCESS_VALIDATIONS 3
[phase] RENDER_RESPONSE 6
#### 5文字「taro5」で送信(成功)
[phase] RESTORE_VIEW 1
[phase] APPLY_REQUEST_VALUES 2
[phase] PROCESS_VALIDATIONS 3
[phase] UPDATE_MODEL_VALUES 4
[model] setName(taro5)
[phase] INVOKE_APPLICATION 5
[action] save() name=taro5
[phase] RENDER_RESPONSE 6
検証に失敗すると4と5が飛ばされ、setterもアクションも呼ばれずに入力画面へ戻ります。アクションが呼ばれるのは、フォームに設定した必須・文字数の検証を通過した後だけです。ただし保証されるのは画面に設定した検証までで、在庫や権限などの業務条件は業務ロジック側で検証します。なお、ボタンに immediate="true" を付けるとアクションの実行が2の直後に早まるため、この順序は既定設定での結果です。逆に、検証エラー時にも何かを実行したい処理(ログ記録など)をアクションメソッドに書いても動きません。
Faceletsによる画面テンプレート
Faceletsは、JSFの画面をXHTMLで書く標準のテンプレート技術です。#{...} のEL(式言語)でビーンのプロパティやメソッドと結びつけ、h:(HTML部品)と f:(検証・変換・Ajaxなどのコア機能)のタグを使い分けます。共通レイアウトは <ui:composition template="..."> と <ui:define> で親テンプレートの差し込み口を埋める形で書き、ヘッダーなどの部品は <ui:include> や複合コンポーネントで共通化します。
出力は既定でHTMLエスケープされます。上の画面に <script>alert(1)</script> を送ると、遷移先の #{userBean.name} は <script> として出力され、スクリプトは実行されませんでした。h:outputText に escape="false" を付けるとこの保護が外れるので、ユーザー入力を含む値には付けないでください。
バッキングビーン:@ManagedBeanからCDI @Namedへ
画面の値とアクションを受け持つクラスをバッキングビーンと呼びます。古い解説記事の多くは @ManagedBean と javax.faces.bean のスコープ注釈で書かれていますが、この方式はJSF 2.3で非推奨になり、Faces 4.0で削除されました(#1547)。Faces 4.0以降で古いコードをそのまま動かすことはできません。
| 旧(Faces 3.0まで) | 新(CDI) |
|---|---|
| @ManagedBean | @Named(jakarta.inject) |
| @RequestScoped(faces.bean) | @RequestScoped(enterprise.context) |
| @SessionScoped(faces.bean) | @SessionScoped(enterprise.context) |
| @ViewScoped(faces.bean) | @ViewScoped(jakarta.faces.view) |
| @ManagedProperty(faces.bean) | @Inject または faces.annotation版 |
件数が多い既存アプリでは、OpenRewriteのレシピ FacesManagedBeansRemoved(内部で UpdateManagedBeanToNamed を含む)を使うと注釈の置き換えを機械的に進められます。CDIビーンを単体テストする方法はCDI-Unitとは?Java CDIのBeanをJUnit 5でテストする設定とweld-junitとの選び方で解説しています。
画面遷移・入力検証・Ajaxの書き方
暗黙的ナビゲーションによる画面遷移
アクションメソッドの戻り値が、そのまま遷移先のビューIDになります。条件で行き先を分けるならJavaの if で戻り値を変えれば足り、多くの画面で設定ファイルは要りません。遷移規則を1か所で管理したいときだけ、faces-config.xml の <navigation-rule> に書きます。戻り値の後ろに ?faces-redirect=true を付けるとリダイレクトになり、ブラウザの再読み込みで二重送信されるのを防げます。
入力検証と日本語メッセージの上書き
必須は required="true"、長さや形式は <f:validateLength>・<f:validateRegex> で宣言します。Jakarta Validation(Bean Validation)を入れれば、ビーンの @NotNull や @Size もProcess Validationsフェーズで効きます。検証の設計全般はバリデーションとは?入力検証の目的・種類・実装パターンと設計を実装者視点で解説を参照してください。
注意したいのは、Mojarra 4.1.18が同梱する日本語メッセージです。日本語ロケールで2文字を送ると、画面には次の文がそのまま表示されました。
f:name: 確 認 の 間 違 い: 長 さ は 正 当 な 最 小 よ り よ り 少 な く '3' あ る
jar内の jakarta/faces/Messages_ja.properties(2020年12月付け)がこの文面で、利用者に見せられる品質ではありません。faces-config.xml に自前のメッセージバンドルを登録し、同じキーで上書きすると直ります。
<!-- faces-config.xml -->
<application>
<message-bundle>demo.messages</message-bundle>
</application>
# src/main/resources/demo/messages_ja.properties
jakarta.faces.validator.LengthValidator.MINIMUM={0}文字以上で入力してください
jakarta.faces.component.UIInput.REQUIRED=入力してください
この設定で、2文字の送信は「3文字以上で入力してください」、空送信は「入力してください」と表示されるようになりました。日本語の業務画面でJSFを使うなら、最初に入れておく設定です。
f:ajaxによる部分更新
<f:ajax> を部品に入れると、ページ全体を読み直さずに一部だけを更新できます。execute で処理する部品を、render で再描画する領域を指定します。
<h:commandButton value="入力を検証">
<f:ajax execute="@form" render="@form" />
</h:commandButton>
Ajaxで2文字を送ると、サーバーは <partial-response> というXMLでフォームの差分とViewStateだけを返し、ログには通常の送信と同じく1・2・3・6のフェーズが出ました。Ajaxでも同じライフサイクルを使いますが、executeで入力処理・検証・モデル更新の対象を、renderで再描画の対象を絞れます。クライアント側のスクリプトはFaces 4.0で jsf.js から faces.js に改名されており、実際のページにも jakarta.faces.resource/faces.js が読み込まれていました。
ViewStateによるCSRF対策と認証の実装
ViewState偽造時の例外とステートレス画面の保護条件
JSFはフォームに jakarta.faces.ViewState という隠しフィールドを自動で埋め込み、次のリクエストでその値からコンポーネントツリーを復元します。既定の状態保存先はサーバー側で、値は推測しにくいキーになっています。この値が外部サイトからは分からないため、ViewStateはCSRF(クロスサイトリクエストフォージェリ)のトークンとしても働きます。2つのパターンで確かめました。
- ViewStateを
123:456に書き換えて送信:Restore ViewでViewExpiredException(ビュー /index.xhtml を復元できませんでした)となりHTTP 500。アクションは実行されない。 - 別セッションからViewStateなしで送信:初回表示として扱われ、フェーズは1と6だけ。アクションは実行されない。
本番では ViewExpiredException をエラーページへ振り分け、セッション切れと同じ「もう一度操作してください」の案内を出すのが定石です。ビューを transient にしてステートレスにするとこの保護はなくなるので、その画面には別のトークン対策が要ります。
もう1点、初回の応答では action="/demo/index.xhtml;jsessionid=…" のようにセッションIDがURLに付いていました。Cookieが使えるか分からない最初のリクエストでTomcatがURLにも埋めるためで、URL経由でセッションIDが漏れる原因になります。web.xmlの <session-config> に <tracking-mode>COOKIE</tracking-mode> を書くと止められます。セッションIDの扱いはセッション管理とは?セッションIDの発行から失効までの実装と保存先選定を解説で詳しく扱っています。
JSFの認証・認可とコンテナ認証・Jakarta Securityの役割
JSF自体は認証機構を持ちません。ログイン画面だけをFaceletsで作り、認証はサーブレットのコンテナ認証(web.xmlのセキュリティ制約)か、Jakarta Securityの @FormAuthenticationMechanismDefinition などに任せる構成が標準です。権限による表示の切り替えは、CDIビーンで判定して rendered 属性で出し分けます。
PrimeFaces・OmniFacesの最新版と選び方
標準部品だけでも画面は作れますが、業務画面ではUIコンポーネントライブラリを足すのが一般的です。代表はPrimeFacesで、データテーブル・日付選択・ダイアログ・チャートなどを揃えています。2026年10月時点の最新は16.0.0で、Jakarta Faces 4.0と4.1に対応し、PrimeFaces自体の最低要件はJava 11です。ただし、Faces 4.1と組み合わせる場合はJava 17以上が必要です。OmniFacesは見た目の部品ではなく、例外処理やコンバータなど標準の足りない部分を補うユーティリティ集で、最新の5.5.5はFaces 4.1以降とJava 17以上が前提です。
選ぶ基準は単純で、画面部品を作り込みたくないならPrimeFaces、例外処理や細かな不便を補いたいならOmniFacesです。両者は役割が重ならないので併用できます。javax版のPrimeFacesを使っている既存アプリは、Faces 4.x対応の版へ上げないとjakarta化の後に起動しません。
JSFを今から採用すべきか:選ぶ条件と避ける条件
結論から書くと、新規のWebアプリでJSFを第一候補にする場面は少数です。新規なら、画面をReactやVueで作りJavaはAPIに徹する構成と、Spring Bootで画面も出すSpring MVCとThymeleafの組み合わせを先に比較します(Spring MVCとは?spring-webmvcの役割とSpring Boot 4での依存名変更を解説、Thymeleaf(タイムリーフ)とは?読み方とSpring Bootでの使い方・th:属性を実例で解説)。
それでもJSFが合理的なのは、次の条件のときです。
- 稼働中のJSFアプリがある:現行の画面資産を再利用でき、利用ライブラリがFaces 4.1に対応するなら、全面刷新より段階的な移行を優先する。
- Javaだけで完結させたい社内システム:フロントエンド専任がいない少人数の体制で、入力フォーム中心の管理画面を保守したい。
- Jakarta EEのアプリケーションサーバーが既にある:WildFlyやPayaraには実装が同梱済みで、追加の依存なしに使える。
逆に、操作の反応速度が売りの画面、スマートフォン向けアプリとAPIを共有したい構成、フロントエンドとバックエンドを別チームで開発する体制では、JSFを選ぶべきではありません。画面イベントをサーバーへ送る頻度や、画面とビーンの結合度を評価してください。APIの共有自体は可能ですが、独立したフロントエンドを主役にする構成ではJSFの画面管理を採用する利点が小さくなります。Strutsなど同世代の資産から移行する場合の判断はStrutsとSpringの違いと移行手順|Struts 1・2系別の対応表とjakarta化の注意点も参考になります。JSFで作られた基幹システムの延命・刷新の判断に迷う場合は、基幹システム開発として現行の構成から相談いただけます。
よくある質問
JSFとは何ですか?
JavaServer Facesの略で、Javaのサーバーサイドで画面を組み立てるJakarta EE標準のWebフレームワークです。画面をUIコンポーネントの木として扱い、入力検証や画面遷移をサーバー側で処理します。仕様名は現在Jakarta Facesです。
JSFの最新バージョンは?
仕様の最新はJakarta Faces 4.1(Jakarta EE 11)です。実装はMojarra 4.1.18とMyFaces 4.1.4が最新の安定版で(2026年10月時点)、次のFaces 5.0はJakarta EE 12向けに策定中です。
.jsfファイルとは何ですか?
多くはファイルではなく、FacesServlet が受け付けるURLの拡張子です。Faces 4.x では web.xml が無くても *.jsf が自動でマッピングされます。中身のテンプレートは .xhtml ファイルです。
JSFとJakarta Facesは違うものですか?
同じ技術です。Jakarta EEへの移管後、3.0ではJakarta Server Faces、4.0からJakarta Facesという名称になりました。3.0(2020年)でパッケージが javax.faces から jakarta.faces に変わりました。
JSFは今でも使われていますか?
既存のJakarta EE業務システムや、Javaだけで保守する管理画面では現役です。仕様は4.1まで改訂され、5.0も策定が進んでいます。新規開発では、SPAとAPIを分ける構成や、Spring MVCとThymeleafによるサーバー描画も比較候補になります。