はじめに
Javaの実務経験はありますが、Spring Bootはまったくの初学者です。
このシリーズでは、Spring Bootをゼロから学びながら「JWT認証付きのタスク管理REST API」を完成させるまでの過程を、全8本の記事にまとめていきます。
| # | テーマ |
|---|---|
| ① | 環境構築編:プロジェクト作成、Hello World(この記事) |
| ② | 核となる概念編:DI/IoCコンテナ、アノテーション駆動 |
| ③ | 最初のREST API編:タスクの基本CRUD(メモリ上) |
| ④ | DB連携編:Spring Data JPA、H2→PostgreSQL |
| ⑤ | 設定・プロファイル編:application.yamlで環境分離 |
| ⑥ | テスト編:JUnit + MockMvc |
| ⑦ | 認証・認可編:ユーザー登録・ログイン・JWT |
| ⑧ | 総仕上げ編:ページネーション・絞り込み、Docker化 |
単に「動くものを作る」だけでなく、「なぜこう書くのか」を自分の言葉で説明できるようになることを目標にしています。
進め方:Claude Codeに「手順書」を作ってもらう
学習のお供として、AIコーディングツールの Claude Code を使っています。ただし、コードはClaude Codeに書かせず、自分の手で書くというルールにしました。
Claude Codeには「実行するコマンド」「作成するファイルのパスと中身」「なぜそう書くのか」をまとめた手順書を作ってもらい、それを見ながら自分で実装して、結果を報告する……という流れで進めています。
全部AIに書かせると早いのですが、それでは自分の理解が追いつかないためです。
この記事のゴール
- Spring Bootのプロジェクトを作成する
-
GET /helloにアクセスするとHello, Spring Boot!が返ってくる - そのエンドポイントを自動テストで確認できる
環境
| 項目 | バージョン |
|---|---|
| OS | Windows 11 |
| Spring Boot | 4.1.1 |
| Java | 21(Eclipse Temurin 21.0.5) |
| ビルドツール | Maven(Maven Wrapper経由) |
| ターミナル | PowerShell |
3.5系ではなく4.1.1を選んだ理由
最初は「長く使われそうな安定版がいいだろう」と考えて、Spring Boot 3.5系を使うつもりでした。
ところが調べてみると、3.5系はOSS版のサポートがすでに終了していました(2026年6月30日)。実際に Spring Initializr を開いても、選択肢には4.0系以降しか出てきません。
これから学ぶのに、サポートが終わったバージョンを選ぶ理由はありません。そこで、執筆時点(2026年10月)で最新の安定版である 4.1.1 を使うことにしました。
ネット上の記事は3系向けのものがまだ多いので、4系では書き方が変わっている部分に注意が必要です。この記事でも、実際に引っかかりそうなポイントを2つ紹介します。
手順1:Java 21 を使えるようにする
Spring Boot 4を動かすには、Java 17以上が必要です。今回は、長期サポート版(LTS)の Java 21 を使います。
さっそく java -version を確認してみたところ……
java version "1.8.0_391"
Java 8でした。 環境変数 Path 上にある、PCに以前から入っていた Java 8 が使われていました。さらに環境変数 JAVA_HOME も、Eclipseの日本語化パッケージである Pleiades に入っている Java 8 を指していました。
幸い、Pleiadesには Java 21 も同梱されていたので(C:\pleiades\2024-12\java\21)、新しくインストールする必要はありませんでした。PowerShellで次のように切り替えます。
$env:JAVA_HOME = "C:\pleiades\2024-12\java\21"
$env:Path = "$env:JAVA_HOME\bin;$env:Path"
java -version
JAVA_HOME は、Mavenなどのツールが「どのJDKを使うか」を判断するための環境変数です。一方、ターミナルで java と打ったときに実行されるのは Path 上で最初に見つかった java です。そのため、2行目で Java 21 の bin フォルダを Path の先頭に追加しています。
ここでは、システム全体の環境変数は変更せず、このターミナルの中だけで切り替えています。 システム全体を変えてしまうと、Java 8を前提にしている他のツールや作業に影響が出るおそれがあるからです。
そのかわり、ターミナルを閉じると設定が消えるので、作業のたびにこの3行を実行する必要があります。
手順2:Spring Initializr でプロジェクトを作る
Spring Initializr は、Spring Bootプロジェクトのひな形をブラウザ上で作れる公式のサービスです。次のように設定して「GENERATE」を押すと、zipファイルがダウンロードされます。
| 項目 | 設定値 | 補足 |
|---|---|---|
| Project | Maven | |
| Language | Java | |
| Spring Boot | 4.1.1 | SNAPSHOT や M(マイルストーン)が付いたものは開発中の版なので選ばない |
| Group | com.example |
|
| Artifact | task-management-api |
|
| Package name | com.example.taskmanagementapi |
ハイフンは自動で取り除かれる |
| Packaging | Jar | |
| Configuration | YAML | 理由は手順4で説明します |
| Java | 21 | |
| Dependencies | Spring Web |
依存関係(Dependencies)は、Spring Web だけにしました。データベースや入力チェックなどの機能は、必要になった回で理由と一緒に追加していく方針です。
ダウンロードしたzipを展開すると、task-management-api フォルダができます。
手順3:生成されたファイルを見る
生成されたプロジェクトの主な構成は次のとおりです。
task-management-api/
├── mvnw / mvnw.cmd ← Maven Wrapper
├── .mvn/
├── pom.xml ← 依存関係などの設定
└── src/
├── main/
│ ├── java/com/example/taskmanagementapi/
│ │ └── TaskManagementApiApplication.java ← 起動クラス
│ └── resources/
│ └── application.yaml ← 設定ファイル
└── test/
└── java/com/example/taskmanagementapi/
└── TaskManagementApiApplicationTests.java
Maven Wrapper(mvnw)とは
私のPCにはMaven本体がインストールされていませんでした。それでも問題なく進められたのは、Maven Wrapper のおかげです。
mvnw.cmd(Windows用)を実行すると、プロジェクトで指定されたバージョンのMavenが自動でダウンロードされて使われます。Mavenを事前にインストールする必要がなく、人によってMavenのバージョンが違う、という問題も起きにくくなります。
pom.xml:4系での名前の変更に注意
pom.xml を開くと、依存関係が次のようになっていました。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
ここが 4系で注意したいポイントその1 です。
3系までの記事では、Web機能の依存関係は spring-boot-starter-web と書かれていることがほとんどです。4系では spring-boot-starter-webmvc という名前になりました。旧名の spring-boot-starter-web も非推奨(deprecated)として残っていますが、これから作るプロジェクトでは新しい名前を使います。
Initializrの画面では、以前と同じ「Spring Web」という表示なので、気づきにくいところです。
テスト用にも、spring-boot-starter-webmvc-test という専用の依存関係が追加されています。
手順4:application.yaml を確認する
Spring Bootの設定ファイルには、properties形式とYAML形式の2種類があります。今回はInitializrで YAML を選んだので、application.yaml が最初から作られています。
spring:
application:
name: task-management-api
同じ内容をproperties形式で書くと、次の1行になります。
spring.application.name=task-management-api
YAMLは、ドットで区切っていた部分をインデントで表します。今はまだ設定が1つだけなのでありがたみがありませんが、④DB連携編や⑤設定・プロファイル編で設定が増えたときに、ぐっと読みやすくなるはずです。
YAMLで気をつけたいのは次の3点です。
- インデントにタブは使えません。 スペースで揃えます(2つずつが一般的です)
-
:の後ろには半角スペースが必要です。name:task-management-apiでは正しく読み込まれません - properties形式とYAML形式のファイルを両方置かないようにします。 同じ場所に両方あると properties 側が優先され、どちらの設定が効いているのか分かりにくくなります
手順5:HelloController を作る
いよいよコードを書きます。起動クラスと同じ階層に hello パッケージ(フォルダ)を作り、その中に HelloController.java を作成します。
src/main/java/com/example/taskmanagementapi/
├── TaskManagementApiApplication.java
└── hello/
└── HelloController.java ← 新規作成
package com.example.taskmanagementapi.hello;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello, Spring Boot!";
}
}
たった数行ですが、ポイントがいくつかあります。
-
@RestController
このクラスが「HTTPリクエストを受け付けて、結果をそのまま返すクラス」であることを示します。@Controllerと@ResponseBodyを合わせたもので、メソッドの戻り値がレスポンスの本文そのものになります。 -
@GetMapping("/hello")
GET /helloへのリクエストが来たら、このメソッドを呼び出す、という対応付けです。 -
newしていないのに動く理由
このクラスは、どこからもnew HelloController()されていません。それでも動くのは、起動クラスに付いている@SpringBootApplicationが、自分と同じパッケージとその配下を自動で探して、@RestControllerなどが付いたクラスを登録してくれるからです(コンポーネントスキャン)。
逆に、起動クラスのパッケージの外(たとえばcom.example.hello)に置くと見つけてもらえず、アクセスしても404になります。
この「Spring が勝手に用意してくれる」仕組みは、次回の②DI/IoC編で詳しく掘り下げる予定です。
手順6:起動して確認する
zipを展開してできた task-management-api フォルダに移動して、アプリケーションを起動します。
cd task-management-api
.\mvnw.cmd spring-boot:run
初回は、Mavenや各種ライブラリのダウンロードがあるため、少し時間がかかります。
ログの中で、特に見ておきたいのは次の行です。
| ログ | 意味 |
|---|---|
Starting ... using Java 21.0.5 |
Java 21で動いている |
Starting Servlet engine: [Apache Tomcat/11.0.24] |
組み込みのWebサーバー(Tomcat)が起動している |
Tomcat started on port 8080 (http) |
8080番ポートでリクエストを受け付けられる状態になった |
Started TaskManagementApiApplication in 1.597 seconds |
起動完了 |
Spring Bootでは、Webサーバーの Tomcat がアプリケーションに組み込まれています。Tomcatを別途インストールして、そこにアプリを配置する……という作業が要らないのは、とても楽だと感じました。
起動したら、ブラウザで http://localhost:8080/hello を開きます。
動きました! 🎉
PowerShellで確認する場合は、curl ではなく curl.exe と書きます。Windowsに標準で入っている Windows PowerShell 5.1 では、curl が別のコマンド(Invoke-WebRequest)の別名になっているためです(PowerShell 7 以降では、この別名はなくなっています)。
curl.exe http://localhost:8080/hello
# → Hello, Spring Boot!
ちょっとした発見:DispatcherServlet は最初のリクエストで動き出す
起動ログの最後の3行(Initializing Spring DispatcherServlet 'dispatcherServlet' など)は、起動完了から約50秒後の時刻になっていました。これは、ブラウザで /hello に初めてアクセスしたタイミングです。
DispatcherServlet は、届いたリクエストを HelloController などの適切なクラスに振り分ける役割を持っています。起動時ではなく、最初のリクエストが来たときに初期化されることが、ログの時刻から分かりました。
停止するとき
起動したターミナルで Ctrl + C を押します。Windowsでは「バッチ ジョブを終了しますか (Y/N)?」と聞かれるので、y を入力します。
なお、停止後にも BUILD SUCCESS と表示されますが、これは「Mavenのコマンドが正常に終わった」という意味で、テストが成功したわけではありません。
手順7:テストを書く
「テストは後回しにしない」と決めているので、Hello Worldの段階からテストを書きます。
もともとあるテスト
実は、Initializrがテストを1本作ってくれています。
@SpringBootTest
class TaskManagementApiApplicationTests {
@Test
void contextLoads() {
}
}
中身は空ですが、@SpringBootTest はアプリケーション全体を本番と同じように組み立てます。そのため、設定ミスなどでSpringの起動に失敗すると、このテストも失敗します。 最低限の安全網として役立つテストです。
HelloController のテストを追加する
テスト側にも同じ hello パッケージを作り、HelloControllerTest.java を作成します。
package com.example.taskmanagementapi.hello;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(HelloController.class)
class HelloControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void helloReturnsGreeting() throws Exception {
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("Hello, Spring Boot!"));
}
}
-
@WebMvcTest(HelloController.class)
Web(Controller)まわりだけを起動する、軽いテストです。アプリケーション全体を組み立てる@SpringBootTestに比べて、速く動きます。 -
MockMvc
実際にサーバーを立てずに、疑似的なHTTPリクエストを送れる道具です。 -
.andExpect(...)
ステータスコードが200(OK)であること、レスポンスの本文がHello, Spring Boot!と完全に一致することを確認しています。
そして、ここが 4系で注意したいポイントその2 です。
@WebMvcTest のパッケージが、3系から変わっています。
| バージョン | import |
|---|---|
| 3系 | org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest |
| 4系 | org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest |
ネット上の記事の多くは3系のimportのままなので、そのままコピーするとコンパイルエラーになります。
テストを実行する
.\mvnw.cmd test
Tests run: 2 は、自動生成された contextLoads と、今回追加した helloReturnsGreeting の2本です。
なお、実行中に次のような WARNING が表示されますが、テストの結果には影響ありません。
Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. ...
WARNING: A Java agent has been loaded dynamically (...byte-buddy-agent-1.18.11.jar)
...
WARNING: Dynamic loading of agents will be disallowed by default in a future release
テスト用のライブラリである Mockito が、実行中に Java のエージェント(実行時にクラスを書き換える仕組み)を読み込んでいることへの警告です。Java 21 から、このような動的な読み込みに対して警告が出るようになりました(JEP 451)。対処方法は⑥テスト編で扱う予定です。
わざとテストを失敗させてみる
テストが本当に間違いを見つけてくれるのか、確かめてみました。HelloController の戻り値を、わざと書き換えます。
return "Hello"; // わざと間違える
テストのほうは「Hello, Spring Boot! が返るはず」のまま、mvnw test を実行すると……
[ERROR] HelloControllerTest.helloReturnsGreeting:22 Response content expected:<Hello, Spring Boot!> but was:<Hello>
[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0
しっかり失敗してくれました。このログから、次のことが読み取れます。
-
HelloControllerTest.helloReturnsGreeting:22:どのテストの何行目で失敗したか(22行目はcontent().string(...)の行) -
expected:<Hello, Spring Boot!> but was:<Hello>:期待していた値と、実際に返ってきた値 -
Tests run: 2, Failures: 1:2本のうち、失敗したのは1本だけ。アプリが起動できるかを確かめるcontextLoadsは成功したまま
つまり、「アプリは起動するけれど、返す内容が間違っている」という状態を、HelloControllerTest が見つけてくれたわけです。2本のテストが、それぞれ別のものを守っていることがよく分かりました。
さらに、失敗したときにはリクエストとレスポンスの詳細も自動で表示されていました。
Status = 200 なのに Body = Hello になっていることから、「ステータスは正常だが、本文が違う」と一目で分かります。原因を探すときに、とても役立つ情報です。
最後に、戻り値を Hello, Spring Boot! に戻して、もう一度 mvnw test を実行すると、再び BUILD SUCCESS になりました。
つまずいたポイントまとめ
| つまずき | 原因 | 対処 |
|---|---|---|
java -version が 1.8 だった |
以前から入っていた Java 8 が使われていた | ターミナル単位で JAVA_HOME を Java 21 に切り替え |
| 3.5系が Initializr にない | OSS版のサポートが終了していた | 最新の安定版 4.1.1 を採用 |
| 依存関係の名前がネット上の記事と違う | 4系で spring-boot-starter-webmvc に変更 |
Initializr が生成した pom.xml をそのまま使う |
@WebMvcTest の import |
4系でパッケージが変更 |
org.springframework.boot.webmvc.test.autoconfigure を使う |
PowerShell の curl
|
Windows PowerShell 5.1 では Invoke-WebRequest の別名 |
curl.exe と書く |
停止時の BUILD SUCCESS
|
Maven コマンドが正常に終わっただけ | テストの成功と取り違えない |
まとめ
今回は、Spring Bootのプロジェクトを作成し、/hello が動いて、テストが通るところまで進めました。
- Spring Boot 4.1.1 + Java 21 でプロジェクトを作成した
-
@RestControllerと@GetMappingだけで、APIのエンドポイントが作れた -
@WebMvcTestとMockMvcで、サーバーを立てずにテストできた - わざと失敗させることで、テストが何を守っているのかを実感できた
一番印象に残ったのは、ネット上の情報がすでに古くなっていることが多いという点です。3.5系のサポート終了、依存関係の名前の変更、@WebMvcTest のパッケージ変更と、今回だけでも3つありました。「この記事はどのバージョン向けか」を意識しながら調べる習慣が大事だと感じました。
次回予告
次回は②核となる概念編です。今回、HelloController は一度も new していないのに動きました。この「なぜ動くのか」を、DI(依存性の注入)とIoCコンテナという考え方から掘り下げていきます。







