StrutsとSpring(Spring MVC)の違いは、処理の中心に何を置くかにあります。StrutsはActionクラスを中心に、XML設定やStruts 2の規約・アノテーションで画面遷移を組み立て、Spring MVCはDIコンテナが組み立てた普通のJavaクラスにアノテーションでURLを割り当てます。この差が設定の量、入力検証の書き方、テストのしやすさに表れます。Struts 1系・2.3系・2.5系は公式サポートが終わっており、新しい脆弱性にも修正版は出ません。この記事では違いをコードで比較し、Struts 1と2で変わる移行の難しさ、javaxとjakartaの壁、段階的な移行手順までを整理します。JDKの更新を含む広い移行はJavaマイグレーションの進め方|JDK 8→21/25の手順とSpring Boot・Jakarta移行で扱い、本記事はStrutsからSpringへの載せ替えに絞ります。
まとめ:StrutsとSpringの設計差と系列別の移行条件
StrutsはActionクラス中心でXMLに遷移と検証を書き、Spring MVCはDIコンテナとアノテーションで組み立てます。Spring MVCではモックを使った単体テストがサーバ無しで書けます。Struts 1.x系は2013年4月5日、2.3.x系は2019年9月12日にEOLとなりました。2.5.x系は2023年10月30日に6か月後のEOLが予告され、2024年4月ごろに終わりました。2026年10月時点で修正版が出ているのは7.4.0と6.12.0だけです。
移行先は、新しく土台を組むならSpring Boot 4.1が第一候補です(OSSサポートは2027年7月31日まで)。Struts 1・2.5はjavax.servlet、Spring Boot 3以降はjakarta.servletで動くため、新旧を同じWARに同居させられず、URL単位で新旧のアプリを振り分ける段階移行が基本です。Struts 2ならSpringのDIを先に入れる2段階移行が取れ、Struts 1は別アプリでの並行稼働が基本です。
StrutsとSpring MVCの設計・設定・入力検証・テストの比較
どちらもJavaのWeb用MVCフレームワークで、リクエストを受けて処理し画面へ渡す役割は同じです。違うのは部品の組み立て方です。
| 観点 | Struts 1 | Struts 2 | Spring MVC |
|---|---|---|---|
| 処理の中心 | Action(継承必須) | Action(POJOも可) | @Controllerのメソッド |
| URLの割り当て | struts-config.xml | struts.xml/規約 | @GetMapping等 |
| 入力の受け皿 | ActionForm(継承必須) | Actionのプロパティ | フォームクラス(POJO) |
| 入力検証 | validation.xml | validation.xml/アノテーション | Bean Validation(@Valid) |
| 共通処理 | RequestProcessor | インターセプタ | HandlerInterceptor/AOP |
| DI | なし | プラグインで外部連携 | 標準 |
| JSON API | 自前実装 | JSONプラグイン | @RestControllerで標準 |
設計の中心:Actionクラス対DIコンテナ
Struts 1のActionはorg.apache.struts.action.Actionを継承し、executeメソッドがServletのリクエストとレスポンスを直接受け取ります。サービスクラスはAction自身が生成するのが一般的でした。Spring MVCでは、コントローラもサービスもDIコンテナが生成してコンストラクタで渡し、コントローラはServlet APIに依存しない普通のクラスになります。同じ「ユーザーを1件表示する」処理を並べます。
// Struts 1:ActionFormを受け、ActionForwardで遷移先を返す
public class UserAction extends Action {
private final UserService service = new UserService();
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest req, HttpServletResponse res) {
UserForm f = (UserForm) form;
req.setAttribute("user", service.find(f.getId()));
return mapping.findForward("success");
}
}
// Spring MVC:DIで受け取ったサービスを使い、ビュー名を返す
@Controller
public class UserController {
private final UserService service;
public UserController(UserService service) {
this.service = service;
}
@GetMapping("/users/{id}")
public String show(@PathVariable Long id, Model model) {
model.addAttribute("user", service.find(id));
return "user/show";
}
}
設定の置き場所:struts-config.xml対アノテーション
Struts 1では、URLとActionの対応、使うActionForm、検証の有無、遷移先をすべてstruts-config.xmlに書きます。画面が数百ある業務システムではこのファイルが数千行になり、コードと設定を行き来しないと処理を追えません。
<action path="/user" type="com.example.UserAction"
name="userForm" scope="request" validate="true"
input="/user/edit.jsp">
<forward name="success" path="/user/show.jsp"/>
</action>
Spring MVCでは、上のコード例の@GetMapping("/users/{id}")のように、URLの割り当てがメソッドの真上に来ます。遷移先はメソッドが返すビュー名("user/show")で、ViewResolverがJSPやThymeleafのテンプレートへ解決します。処理の入口から出口までを1つのクラスで読めることが、保守での一番の違いです。主要なアノテーションの役割はSpringアノテーションとは?仕組みと主要アノテーションの使い分けを解説にまとめています。
入力検証:ActionFormとvalidation.xml対Bean Validation
Struts 1では、入力値の受け皿としてActionFormを継承したクラスを作り、入力検証はActionFormのvalidateメソッドに実装するか、ValidatorプラグインとValidatorFormなどを使ってvalidation.xmlに定義します。Spring MVCでは、フォームクラスは継承のない普通のクラスで、検証ルールはJakarta Bean Validationのアノテーションとしてフィールドに付けます。
public class UserForm {
@NotBlank
@Size(max = 50)
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
@PostMapping("/users")
public String create(@Valid @ModelAttribute UserForm form, BindingResult result) {
if (result.hasErrors()) {
return "user/edit";
}
service.register(form);
return "redirect:/users";
}
移行で挙動が変わりやすいのはここです。空白だけの入力の扱いや、けた数を文字数とバイト数のどちらで数えるかは、独自Validatorを足している現場ほど実装ごとに違います。@NotBlankは空白だけの文字列を不正とし、@SizeはStringに付けるとString.length()、つまりUTF-16のコード単位数を数えます。絵文字などの補助文字は1文字で2と数えられ、見た目の文字数と一致しないことがあります。全角を2バイトとして数える旧仕様を残したいなら、カスタムの制約アノテーションを作る必要があります。検証設計の考え方はバリデーションとは?入力検証の目的・種類・実装パターンと設計を実装者視点で解説も参考になります。
テストのしやすさ:MockMvcによるコントローラの単体テスト
Struts 1のActionのテストにはHttpServletRequestやActionMappingの模擬(StrutsTestCase等)が必要で、実際はサーバに配備して画面で確かめる運用が多くなりがちでした。Spring MVCではMockMvcで、サーバを起動せずにURL・ビュー名・入力エラーまで検証できます。次のテストはSpring Boot 4.1.1で2件とも成功を確認しています。検証構成にはWeb MVC・Validation・Thymeleaf・Web MVC Testの各スターター、ビューのテンプレート、UserServiceとUserの定義を含めています。掲載コードではimportと補助クラスを省略しています。
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
MockMvc mvc;
@MockitoBean
UserService service;
@Test
void show() throws Exception {
when(service.find(1L)).thenReturn(new User(1L, "Sato"));
mvc.perform(get("/users/1"))
.andExpect(status().isOk())
.andExpect(view().name("user/show"))
.andExpect(model().attributeExists("user"));
}
@Test
void createRejectsBlankName() throws Exception {
mvc.perform(post("/users").param("name", ""))
.andExpect(view().name("user/edit"))
.andExpect(model().attributeHasFieldErrors("userForm", "name"));
}
}
Spring Boot 4では@WebMvcTestのパッケージがorg.springframework.boot.webmvc.test.autoconfigureに変わり、依存はspring-boot-starter-webmvc-testです。3.x系の記事にあるorg.springframework.boot.test.autoconfigure.web.servlet.WebMvcTestと@MockBeanのままでは4系でコンパイルが通りません。
Struts 1とStruts 2の違いで変わる移行の難しさ
Struts 1と2は名前を引き継いだだけの別物です。Struts 2はWebWorkを母体に作られ、ActionFormがなく、入力値はActionのプロパティへ直接入ります。
| 項目 | Struts 1 | Struts 2(2.5/6) | Struts 7 |
|---|---|---|---|
| 最終版 | 1.3.10 | 2.5.33/6.12.0 | 7.4.0 |
| サポート | 2013年終了 | 2.5は終了/6は継続 | 継続 |
| Servlet API | javax | javax | jakarta |
| Java(公式要件) | – | 2.5は7以上/6は8以上 | 17以上 |
| Actionの基底 | Action | xwork2.ActionSupport | struts2.ActionSupport |
| 値の受け渡し | request属性 | OGNL/ValueStack | OGNL/ValueStack |
Struts 7.4.0のActionは、フォーム値をセッターで受け、画面へ渡す値をゲッターで公開します。
// Struts 7:フォーム値はActionのプロパティへ直接入る
public class UserAction extends ActionSupport { // org.apache.struts2.ActionSupport
private final UserService service = new UserService();
private Long id;
private User user;
@Override
public String execute() {
user = service.find(id);
return SUCCESS;
}
@StrutsParameter // org.apache.struts2.interceptor.parameter.StrutsParameter
public void setId(Long id) { this.id = id; }
public User getUser() { return user; }
}
Struts 7.4.0の既定設定(default.properties)はstruts.parameters.requireAnnotations=trueで、@StrutsParameterを付けていないセッターにはリクエストパラメータが入りません。6系から上げると、この設定のために値が空になることがあります。また、Struts 7ではcom.opensymphony.xwork2パッケージが無くなり、ActionSupportはorg.apache.struts2.ActionSupportになりました。7.4.0のjarにcom/opensymphony/xwork2/ActionSupport.classは無く、6.12.0には新旧両方があります。2.5から7へ上げる場合もimportの書き換えとjakarta化が要り、作業はSpringへの移行と重なります。
移行が難しいのはStruts 1です。ActionFormとActionの2クラスをフォームクラスとコントローラへ組み替え、遷移もfindForwardの名前からビュー名へ変わります。Struts 2はActionのプロパティがほぼそのままフォームクラスになり、JSPのOGNL式(<s:property value="user.name"/>)をJSTLやThymeleafの式へ置き換える作業が中心です。
Strutsのサポート終了日と脆弱性の状況
判断材料は、使っている系列に修正版が出るかどうかです。Apache StrutsのEnd-of-Life Versionsのページには、EOLになった系列について「no longer provides security patches, bug fixes, or updates」と書かれています。
| 系列 | EOL日 | 現在の状態 |
|---|---|---|
| Struts 1.x | 2013年4月5日 | EOL(最終1.3.10) |
| Struts 2.3.x | 2019年9月12日 | EOL(最終2.3.37) |
| Struts 2.5.x | 2024年4月ごろ(2023年10月30日に予告) | EOL(最終2.5.33) |
| Struts 6.x | – | 6.12.0が配布中 |
| Struts 7.x | – | 7.4.0が最新 |
2.5.xの日付には注意が要ります。EOL一覧の「30 October 2023」は告知の日付で、2023年の告知の本文は「will reach its end of life in 6 months」です。修正が止まったのは2024年4月ごろで、どちらの日付でも期限は過ぎています。2.3.xは2019年9月12日の告知で、半年前の予告を経てEOLになりました。公式のダウンロードページには7.4.0と6.12.0が並び、6系も配布が続いています。
CVE-2024-53677(S2-067)とS2-069の影響範囲
EOLの系列が危ない例が、CVE-2024-53677(S2-067)です。ファイルアップロード処理のパラメータを細工するとパストラバーサルが起き、条件次第でリモートからのコード実行につながります。Apache Strutsの2024年12月10日の告知によれば影響範囲は2.0.0〜2.3.37、2.5.0〜2.5.33、6.0.0〜6.3.0.2で、ApacheのCNAが付けたCVSS v4.0の基本値は9.5です。公式の対処は6.4.0以降へ上げたうえで新しいアップロード機構(ActionFileUploadInterceptor)へ書き換えることで、旧来のFileUploadInterceptorを使っていなければ影響を受けません。Apache公式の修正版を利用するには、サポート対象系列への更新が必要です。更新までの措置として旧アップロード機構を停止する方法や、商用延長サポートの修正版を利用する選択肢もあります。
2026年1月にはXML設定の読み込みに関するS2-069(CVE-2025-68493)が公表され、影響は2.0.0〜2.3.37、2.5.0〜2.5.33、6.0.0〜6.1.0です。こちらは6.1.1で修正済みでしたが、EOLの2.x系には修正版がありません。経緯と回避策はS2-069(CVE-2025-68493)とは|Apache Struts 2のXXE脆弱性の影響範囲と対策で詳しく扱っています。
移行先の選び方:Spring MVC単体、Spring Boot、Struts 7
Spring MVCとSpring Bootは対立する選択肢ではありません。Spring MVCはWebのMVCを担う本体(spring-webmvc)で、Spring Bootはその初期設定・依存バージョン管理・組み込みTomcatの起動を引き受ける仕組みです。Spring Bootのアプリの中ではSpring MVCが動いています。両者の関係はSpring MVCとは?spring-webmvcの役割とSpring Boot 4での依存名変更を解説【2026年最新】で詳しく説明しています。
新しく土台を組むならSpring Boot 4.1(Java 17以上)を選びます。spring.ioのサポート表では、4.1.xのOSSサポート終了が2027年7月31日、商用サポート終了が2028年7月31日です。3.5.xはOSSサポートが2026年6月30日に終わっており、以降の修正版は商用サポート(2032年6月30日まで)の契約が前提になります。版ごとの期限はSpring Bootバージョン一覧とサポート期限【2026年8月】最新4.1と3.5 EOL後の選び方で確認できます。
Spring MVC単体のWAR構成は、WebLogicやJBoss EAPなど既存のAPサーバへ配備する決まりがある場合に選びます。Spring Framework 6以降はjakartaで動くので、APサーバもjakarta対応版が必要です。Struts 7を選ぶ条件は、よくある質問で扱います。
国内では、NTTデータが2016年2月24日にTERASOLUNA Server Framework for Java 5の本格展開を発表し、Struts 1のセキュリティ問題を背景に基盤をSpring MVC・Spring Securityへ切り替えました。採用判断では、Spring経験者を確保できるか、自社で使うライブラリの資料と保守体制がそろうかも確認します。
javaxとjakartaの壁:新旧を同じアプリに同居させられない理由
移行計画で最初に決めるのは、新旧のコードを同じWARに入れるか別アプリに分けるかで、それを決めるのがServlet APIの名前空間です。Struts 1.3.10と2.5系・6系はjavax.servletのAPIでコンパイルされており、Struts 6.12.0のpomもjavax.servlet-apiに依存しています。一方、Spring Framework 6以降(Spring Boot 3以降)とStruts 7はjakarta.servletで動きます。1つのWARは1つの名前空間のサーブレットコンテナでしか動かないため、Struts 1のActionとSpring Boot 4のコントローラを同じWARに並べることはできません。
Struts 2の段階移行:struts2-spring-pluginによるDIの先行導入
Struts 2には公式のstruts2-spring-pluginがあり、Maven Centralでは7.4.0まで本体と同じ版で公開されています。このプラグインを入れると、ActionのインスタンスをSpringのDIコンテナが生成し、サービスクラスをコンストラクタやセッターで注入できます。移行は2段階で進められます。
- 第1段階:Struts 2のまま、Actionに書かれた業務ロジックをSpringのサービスクラスへ移し、Actionを薄くする。画面とURLは変えない。
- 第2段階:薄くなったActionを、URL単位でSpring MVCのコントローラへ置き換える。サービスクラスはそのまま使い回せる。
Struts 1の段階移行:別アプリでの並行稼働
Spring Framework 3.2系までは、Struts 1のActionをSpringのBeanとして扱うspring-strutsモジュール(DelegatingActionProxyなど)がありました。しかしこのモジュールはソースコードに「@deprecated as of Spring 3.0」と書かれ、Maven Central上の最終版は3.2.18.RELEASEで、4.0以降は提供されていません。専用モジュールが無くても、javaxで動くSpring 5.3以前ならWebApplicationContextUtilsでActionからサービスBeanを取り出せるため、DIの先行導入自体は可能です。ただしSpring 5.3のOSSサポートは2024年8月31日に終わっており、サポート切れの部品を2つ抱えることになります。Spring Boot 4を目標にするなら、別アプリでの並行稼働を基本にします。
Struts 1の場合は、旧アプリはjavaxのまま現行のTomcat 9などで動かし、新しいSpring Bootアプリを別に立てて、リバースプロキシでURLごとに振り分けます。ログイン状態は旧アプリのHttpSessionに入っているため、Spring Session+RedisでCookie・保存先・属性の直列化形式とクラス互換性をそろえてセッションを共有するか、移行期間だけ共通の認証基盤(SSO)を前段に置くかを先に決めます。この振り分け方はストラングラーフィグパターンと呼ばれ、切り戻し設計までストラングラーフィグパターンとは?レガシー移行の実装手順と切り戻し設計を解説で解説しています。
StrutsからSpring MVCへの移行手順
手順は「棚卸し→対応づけ→段階移行→同等性の検証」の4つです。
手順1:現行資産の棚卸しと最初に移す範囲の決定
struts-config.xml(Struts 2ならstruts.xml)からActionの一覧を機械的に抜き出し、JSP・ActionForm・独自タグ・共通の基底Action・サーブレットフィルタ・帳票やバッチとの連携を、機能ブロックごとに表にします。Strutsのjar・JDK・APサーバの版も記録します。最初に移すのは、利用頻度が低く独立した参照系の画面です。ここで移行の型(パッケージ構成、画面テンプレート、検証メッセージの出し方)を決めると、残りは型の繰り返しになります。
手順2:コンポーネントの対応づけ
次の表は部品を対応づける出発点です。定型の部品でも、フォームのスコープ、型変換、検証の実行順、遷移先の扱いを確認し、旧実装と同じ挙動になるか判断します。独自の基底Actionやフィルタで作った認証はSpring Securityとは?認証・認可の仕組みと基本設定をわかりやすく解説【2026年版】の設定へ置き換えます。
| Struts側 | Spring MVC側 |
|---|---|
| Actionのexecute | @Controllerのハンドラメソッド |
| struts-config.xmlのaction | @GetMapping/@PostMapping |
| ActionForm | フォームクラス+@ModelAttribute |
| ActionForward | ビュー名(String)を返す |
| validation.xml | Bean Validation(@Valid) |
| DispatchAction | メソッドごとのマッピング |
| Struts 2のインターセプタ | 役割に応じてInterceptor/Filter/Security等 |
| Tiles | Thymeleafのレイアウト |
| ApplicationResources.properties | messages.properties(MessageSource) |
画面をJSPのまま残すか、Thymeleafへ書き換えるかもここで決めます。実行可能jarではJSPがサポートされないため、jar配布を選ぶならThymeleafなどを使います。JSPを残す場合は、対応するコンテナでのWAR配布を検討します(Thymeleaf(タイムリーフ)とは?読み方とSpring Bootでの使い方・th:属性を実例で解説)。JSPを残す場合は、Strutsタグ(<html:form>、<bean:write>)をSpringのformタグとJSTLへ置き換えます。
手順3:機能ブロック単位の段階移行と並行稼働
機能ブロックを1つずつSpring側へ移し、移した分のURLだけを新しいアプリへ振り分けます。URLの振り分けに加え、DBスキーマ・保存データ・セッションを旧アプリでも扱える状態に保ち、切り戻し手順を事前に検証します。新側だけが扱えるデータを保存した場合は、振り分けを戻すだけでは復旧できません。全機能が移ったら旧Strutsアプリを撤去します。
手順4:旧システムとの入出力・更新結果の同等性検証
移行の合否は、同じ入力に対する出力に加え、DB更新、トランザクション、権限判定、外部連携の結果が旧システムと一致するかで判断します。移行前に主要画面とAPIの入出力(HTML、JSON、CSV、帳票)を記録し、移行後に突き合わせます。差が出やすいのは、入力検証の境界値(空白のみ、全角・半角、最大けた数)、日付と数値の書式、文字コード(Shift_JISのままの画面)、エラーメッセージの文言の4つです。
移行で失敗するパターンと急いで移さなくてよい条件
自動変換ツールで大半が片づくと見積もると、独自処理の変換と検証に必要な工数を過小評価するおそれがあります。定型のActionやActionFormは変換が効きますが、工数がかかるのは独自の画面遷移制御、共通の基底Action、帳票やバッチとの連携です。手順1で定型のActionと独自処理を持つActionを分けて数えてから工数を出してください。
移行と機能改善を同じリリースに含めると、不具合が移行と改修のどちらによるものか切り分けられません。改善は移行後の別リリースで行います。
逆に、全面移行を急がなくてよい条件もあります。数年以内に廃止が決まっている、インターネットに公開しておらずファイルアップロード機能も持たない社内システムである、といった条件がそろうなら、脆弱性が突かれる経路は小さくなります。その場合は外部公開やアップロード機能のある画面から移し、残りは廃止まで延命する判断も合理的です。延命用にHeroDevsなどの商用延長サポートがあることも、Struts公式のEOLページに記載されています。移行の計画や見積もりを外部に相談する場合は、システムマイグレーション・リプレイスの支援も選択肢になります。
よくある質問
Strutsの後継フレームワークは何ですか?
Apacheが定めた後継はありません。Struts自体は7.4.0まで開発が続いています。国内の移行例としては、NTTデータがTERASOLUNA Server Framework for Java 5でSpring MVCを基盤に採用しています。
Struts 6や7へ上げるのとSpringへ移すのは、どちらがよいですか?
Struts 2.5の資産が大きく、画面を作り直す予算が無いならStruts 6.12.0または7.4.0へ上げる選択はあります。ただし7系はjakarta化とJava 17以上への対応が必要で、Servlet APIや移動したStrutsクラスを参照するimportの修正が発生し、作業はSpringへの移行と重なります。Spring経験者を確保でき、既存のStruts独自拡張を整理できるなら、Springを長期保守の移行先として優先します。Struts 1からはActionFormもタグも互換が無く、どちらへ移っても書き直しなのでSpringを選びます。
TERASOLUNAとSpringフレームワークの違いは何ですか?
TERASOLUNA Server Framework for Java(5.x)は、NTTデータが提供する開発フレームワークで、Spring MVC・Spring Security・Spring Frameworkを基盤に採用しています。Springと対立する別物ではなく、Springの上に開発ガイドラインと共通ライブラリを足したものです。Struts 1系ベースだった旧TERASOLUNAからの移行にも本記事の手順が当てはまります。
SpringとServletの違いは何ですか?
Servletは、Javaでリクエストを受けてレスポンスを返すための標準API(現在はJakarta Servlet)です。Spring MVCはServletの上に作られたフレームワークで、DispatcherServletが自身にマッピングされたリクエストを受け、URLに応じて各コントローラへ振り分けます。StrutsもActionServlet(2系はフィルタ)を入口にする同じ構造で、どちらもServletを直接書く手間を減らすための仕組みです。
Strutsからの移行にはどのくらいの期間がかかりますか?
画面数、独自拡張の量、テスト資産の有無で大きく変わるため、一律の目安は出せません。小さな範囲を1周移してAction1本あたりの実績工数を測り、定型と独自処理ありに分けて残りの本数に掛け、共通基盤、結合テスト、データ移行、切り替え作業の工数を別途加えて見積もります。