テストでPostgreSQL/MySQL/RedisをDockerで立ち上げたいのに、毎回 @DynamicPropertySource でURLやポートを書き足して…「またこの儀式か〜」ってなりませんか?
しかも、ちょっと依存が足りないだけで「接続先が入ってない!」って落ちたり、Docker ComposeとTestcontainersの話が頭の中で混ざって迷子になったり。あるあるです。
この記事では、Spring Boot 3.1+で使える @ServiceConnection を使って、そのしんどさを一気に減らします。
やることはシンプルで、
「このコンテナにSpringをつなげてね」って合図を出す
だけ。するとSpring Bootが、接続に必要な情報(URL/ホスト/ポート/ユーザー/パスワードなど)をテストに自動で注入してくれます。
先に最短の結論だけ言うと、起動するのはTestcontainers、つなぐのは @ServiceConnection です。
まずは「いちばん小さい成功例」で“動いた!”を作って、そのあとに「付ける場所」「必須依存」「Composeとの違い」「よくあるエラーの直し方」まで順番に片づけていきます。
今日から最小設定でテスト用DB接続、ラクにしちゃいましょう。
@ServiceConnectionを一言で言うと?何を自動でやる?
まず結論:「このコンテナにSpringをつなげてね」の合図
@ServiceConnection は超ざっくり言うと、「このコンテナにSpringをつなげてください」っていう合図です。
テストでコンテナ(PostgreSQLやRedisなど)を立てたあと、Spring Bootが「じゃあ接続先メモを作って、必要な場所に入れておきますね!」とやってくれます。
覚えるフレーズはこれでOKです👇
合図(@ServiceConnection)=橋渡し=接続先メモを自動注入
何が省ける?(URL/ユーザー/パス/ホスト/ポートの手書き)
今まで @DynamicPropertySource で手書きしてた、こういうやつが消えます👇
つまり「コンテナの情報を読んで、Springの設定に書き込む作業」を、Spring Bootが肩代わりしてくれるイメージです。
どこまで自動?(コンテナ起動 vs 接続情報の注入)
ここが混ざりやすいので、分けて覚えるのがコツです。
なので、@ServiceConnection は「勝手に全部やってくれる魔法」ではなく、起動したコンテナとSpringの間をつなぐ橋渡しだと思ってください。ここが分かると、一気に迷子が減ります。
いちばん小さい成功例(PostgreSQL/Redis)を1画面で
理屈はあとです。まずは「え、URL書いてないのに動いた…!」を作ります。
ポイントはこれだけです👇
コンテナ(Testcontainers)に @ServiceConnection を付ける → Springが接続先メモを自動で入れる
Java(JDBC)最小例:@Testcontainers + @Container + @ServiceConnection
@SpringBootTest
@Testcontainers
class PostgresIT {
@Container
@ServiceConnection // ←「このコンテナにSpringをつなげてね」の合図
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine");
@Autowired JdbcTemplate jdbc;
@Test
void it_works() {
Integer one = jdbc.queryForObject("select 1", Integer.class);
assertThat(one).isEqualTo(1);
}
}
ここがポイント(3つだけ)
Kotlin最小例:companion object と @JvmFieldの注意
Kotlinはここでコケやすいです。“staticフィールド扱い”にするのが大事で、そこで @JvmField が登場します。
@SpringBootTest
@Testcontainers
class PostgresIT {
companion object {
@Container
@ServiceConnection
@JvmField // ←これがないと「static扱い」にならず、見つけてもらえないことがあります
val postgres = PostgreSQLContainer("postgres:16-alpine")
}
@Autowired lateinit var jdbc: JdbcTemplate
@Test
fun itWorks() {
val one = jdbc.queryForObject("select 1", Int::class.java)
assertThat(one).isEqualTo(1)
}
}
ここがポイント(3つだけ)
Redis例:アプリ側設定ゼロで疎通する感覚をつかむ
Redisも「host/portどこだっけ…」が消えます。コンテナだけ置いて、あとはSpringに任せます。
@SpringBootTest
@Testcontainers
class RedisIT {
@Container
@ServiceConnection // ←redis:~ のコンテナなら、接続先メモを自動で入れてくれます
static GenericContainer<?> redis =
new GenericContainer<>("redis:7-alpine").withExposedPorts(6379);
@Autowired StringRedisTemplate redisTemplate;
@Test
void it_works() {
redisTemplate.opsForValue().set("hello", "world");
assertThat(redisTemplate.opsForValue().get("hello")).isEqualTo("world");
}
}
ここがポイント(3つだけ)

次は「これ、どこに付けるのが正解?」問題を、2つの流派でスパッと整理します。
どこに付けるの?2つの流派を図で整理(A:staticフィールド / B:@Bean)
「@ServiceConnection って、結局どこに付けるんですか?」で止まる人、多いです。
結論から言うと、Spring Bootが見つけられる形は 2つ だけです👇

ポイントは「Springが見つけられる形」です。
“そのコンテナ、Springから見えてます?” ここだけ外すと、接続先メモが注入されません。
A案:テストクラスの static @Container(いちばん楽)
これが最短ルートです。テストクラス内に static(Kotlinなら companion object + @JvmField)で置きます。
@SpringBootTest
@Testcontainers
class PostgresIT {
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine");
// ... tests
}
A案のいいところ
A案の注意
B案:@TestConfigurationで@Bean(複数テストで使い回す)
「いろんなテストで同じDBコンテナ使いたい」「設定を1か所にまとめたい」ならこっちです。
SpringのBeanとしてコンテナを登録して、そこに @ServiceConnection を付けます。
@SpringBootTest
class PostgresIT {
@TestConfiguration(proxyBeanMethods = false)
static class ContainerConfig {
@Bean
@ServiceConnection
PostgreSQLContainer<?> postgres() {
return new PostgreSQLContainer<>("postgres:16-alpine");
}
}
// ... tests
}
B案のいいところ
B案の注意
どっちを選ぶ?(判断基準:使い回し/拡張/見通し)
迷ったら、このルールでOKです👇
そして共通の大事ポイントをもう一回だけ言います。@ServiceConnection は “Springが見つけられる形(static or Bean)” に置かないと効きません。
ここさえ守れば、「接続情報の手書き」はかなり消せます。

次は、ここまでやっても「なぜか接続できない…」を防ぐための 必須チェックリスト に行きます。
必須チェックリスト(ここが抜けると接続できない)
「コードは合ってそうなのに、なぜか接続できない…」は、だいたい 前提 or 依存 or 置き場所 のどれかが抜けてます。
ここは3分で終わる YES/NOチェックで潰しちゃいましょう。
Spring Bootのバージョンと前提(JUnit5 / Docker起動)
依存関係:何をtestに足す?(Boot側/TC側)
ここ、箱で分けると一気に分かりやすいです👇
① Boot側(Springが“自動注入”するためのやつ)
② Testcontainers側(コンテナを立てるやつ)
ざっくり言うと、起動するための依存(TC) と つなぐための依存(Boot) が両方必要です。片方だけだとハマります。
※この章は「何を足す?」が目的なので、ビルドツールは好みでOKです(Maven/Gradleどっちでも動きます)。
Springが「見つけられる形」になってる?(static or Bean)
最後のチェックがこれです。見落とし率高めです。
このチェックで引っかかるところを直すだけで、「接続先が入ってない」「なんかnullっぽい」系の事故はかなり減ります。

次は、頭がごちゃつきやすい Docker Compose連携とTestcontainers連携の違い をスッキリ分けます。
Docker Compose連携とTestcontainers連携の違い(ごちゃごちゃ解消)
目的が違う:開発環境を立てる vs テストで完結させる
まずここで混ざりがちなので、目的で分けるのが一番ラクです。
なので、基本の合言葉はこれです👇
開発=Compose / テスト=Testcontainers
Composeのとき@ServiceConnectionは必要?(必要な場面/不要な場面)
ここ、結論から言うと “ケースによる” です。ポイントは、Springが「接続先メモ」を作る材料を持っているかどうかです。
✅ 不要になりやすい場面(多くの人はここ)
※この場合、@ServiceConnection を付ける場所(コンテナの実体)がSpring側に存在しないので、合図の出しようがないです。
✅ 必要になる場面(テストに寄せたいとき)
「Composeも使うけど、テストはテストで自己完結させたい」なら、基本はこっちの考え方です。
ざっくりまとめると👇
迷ったらこの早見ルール(開発・CI・ローカル)
迷ったら、いったんこのルールで決めちゃってOKです。
| シーン | おすすめ | 理由 |
|---|---|---|
| ローカル開発(手元で動かす) | Compose | いつでも同じ環境をサッと立てたいからです |
ローカルテスト(./gradlew test / mvn test) | Testcontainers + @ServiceConnection | テストが勝手に立てて勝手に片付けるのがラクだからです |
| CI(GitHub Actionsなど) | まずはTestcontainers | “テストが自己完結”が一番事故りにくいです(外部依存を減らせます) |
要するに、Composeは「開発の再現」、Testcontainersは「テストの完結」。
この線引きができると、「Composeの設定とテストの設定が二重になってしんどい」みたいな混線がかなり減ります。

次は、いよいよ困ったときに一番助かる “よくあるエラー別:原因→直し方” に行きます。
よくあるエラー別:原因→直し方(最短で復旧する)
ここは「ログ全文は読めない!」でも復旧できるように、症状の見た目で分けます。
型はいつもこれです👇
- 症状
- 原因トップ3
- 確認
- 直し方
「接続先が入ってない」:依存不足 / アノテーション位置ミス
症状あるある
原因トップ3
spring-boot-testcontainersなど Boot側の連携依存が足りない@ServiceConnectionの 付ける場所が違う(テストクラスに付けてる等)- コンテナが Springから見える形になってない(staticじゃない / @Beanじゃない)
確認
直し方
「ポートが違う」:固定ポート思考を捨てて“割り当て後ポート”へ
これ、ほぼ全員が一回やります。
コンテナの5432/6379はコンテナ内部の番号で、PC側は別番号に割り当てられます。
イメージ👇(固定ポート期待=×)
× localhost:5432 に行く(固定)
○ 割り当て後ポートに行く(自動注入 or mapped port)
症状あるある
原因トップ3
localhost:5432を 決め打ちしてる- Composeの設定と混ざって、手書き設定が優先されてる
@ServiceConnectionが効いてなくて、デフォルト接続先に行ってる
確認
直し方
「コンテナは起動したのに接続できない」:起動待ち/ネットワーク/権限
症状あるある
原因トップ3
- 起動待ち不足(DBが“起動中”なだけ)
- ユーザー/パスワードなど初期値のズレ
- コンテナの ネットワーク周り(別サービス連携時に多い)
確認
直し方
CIで落ちる:Docker無し/リソース不足/並列実行
症状あるある
原因トップ3
- CIに Dockerがない/使えない
- メモリ不足など リソース不足で起動が遅い
- テスト並列でコンテナが増えて 重くなる
確認
直し方
- Dockerが使える実行環境にする(CIの設定でここが最重要です)
- 並列を落とす/テストを軽くする/タイムアウトを伸ばす、のどれかで安定しやすいです

次は、ちょっと厄介な「勝手に接続が2つ作られて混乱する(JDBCとR2DBCなど)」問題を、スッキリ整理して制御します。
複数接続が勝手に作られて混乱する問題(JDBCとR2DBCなど)
何が起きてる?(自動設定が“両方作ろう”とするケース)
「え、同じPostgreSQLコンテナなのに、DataSource(JDBC)もConnectionFactory(R2DBC)も作ろうとしてません?」みたいな現象です。
だいたい原因はシンプルで、Starterを両方入れてるだけです(例:spring-boot-starter-jdbc と spring-boot-starter-data-r2dbc を同居)。
さらにSpring Boot側は、1つのコンテナから JDBC用/ R2DBC用の接続先メモ(ConnectionDetails)を作れるので、両方候補が揃ってしまうんですね。
type属性で「作る接続情報をしぼる」例
ここで効くのが @ServiceConnection の type属性です。「今日はJDBCだけでいいです!」って絞れます。
JDBCだけにしたい(Java)
@Container
@ServiceConnection(type = JdbcConnectionDetails.class)
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");
JDBCだけにしたい(Kotlin)
@Container
@ServiceConnection(type = [JdbcConnectionDetails::class])
@JvmField
val postgres = PostgreSQLContainer("postgres:16-alpine")
逆にR2DBCだけなら R2dbcConnectionDetails を指定する、って感じです。
それでも迷う時の整理(どのStarterを入れてる?)
迷ったら、まずこれだけ確認してください👇
結論:Starterを減らすか、typeで絞る。この2択でほぼ片付きます。
対応サービス/対応外の逃げ道(自作コンテナでも詰まない)
よく使う対応例(DB/Redis/RabbitMQなど)と選び方
まず安心ポイントです。@ServiceConnection は「よくあるやつ」にはちゃんと対応してます。
Spring Boot側(spring-boot-testcontainers)には、コンテナの種類に応じて接続先メモ(ConnectionDetails)を作る仕組みが用意されています。
よく見るところだとこんな感じです👇
選び方はシンプルで、迷ったらこの順です。
- Testcontainersに「専用Containerクラス」がある?(あるならそれを使う)
GenericContainerしかない? → イメージ名で推測できるか、ダメなら次の保険へ
対応外なら保険:@DynamicPropertySource / DynamicPropertyRegistrar
「対応外っぽい…」は負けじゃないです。保険ルートがちゃんとあります。
要するに、対応してたら@ServiceConnection、対応してなさそうなら動的プロパティでOKです。
さらに踏み込む:独自対応(拡張)への導線(考え方だけ)
「自作コンテナでも@ServiceConnectionしたい!」ってときは、考え方はこうです👇
なので上級者向けには、「自分のコンテナに合うConnectionDetailsを用意して、Springに見つけてもらう」みたいな拡張もできます(深追いは別記事級です)。

次はラスト!「結局どれ選ぶ?」を早見表+コピペテンプレで迷いゼロにします。
最後に:どれを選ぶ?早見表 + コピペ用テンプレ(CTA)
選び方早見表(Compose / Testcontainers / 動的プロパティ)
| やりたいこと | まずこれ | ひとことで |
|---|---|---|
| 手元の開発環境を毎回サッと立てたい | Docker Compose | 「外で立てて、アプリはつなぐだけ」 |
| テストを“それ単体で完結”させたい | Testcontainers + @ServiceConnection | 「テストが立てて、Springが勝手につなぐ」 |
| 対応外サービス/自作コンテナでも確実にいきたい | @DynamicPropertySource(保険) | 「最後はプロパティを自分で差し込む」 |
迷ったら基本は テスト=Testcontainers + @ServiceConnection でOKです。
コピペ置き場(Java/Kotlin・JDBC/R2DBCの最小テンプレ)
Java(JDBC)最小テンプレ
@SpringBootTest
@Testcontainers
class DbIT {
@Container
@ServiceConnection // URL/ユーザー/パス等をSpringに自動注入
static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:16-alpine");
@Autowired JdbcTemplate jdbc;
@Test void ok() {
jdbc.queryForObject("select 1", Integer.class);
}
}
Kotlin(JDBC)最小テンプレ
@SpringBootTest
@Testcontainers
class DbIT {
companion object {
@Container
@ServiceConnection
@JvmField // ←static扱いにする保険
val db = PostgreSQLContainer("postgres:16-alpine")
}
@Autowired lateinit var jdbc: JdbcTemplate
@Test fun ok() {
jdbc.queryForObject("select 1", Int::class.java)
}
}
Java(R2DBC)最小テンプレ(※R2DBCを使う人向け)
@SpringBootTest
@Testcontainers
class R2dbcIT {
@Container
@ServiceConnection // R2DBC側も自動で接続先が入ります
static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:16-alpine");
@Autowired DatabaseClient client;
@Test void ok() {
client.sql("select 1").fetch().one().block();
}
}
「JDBCとR2DBCが両方作られて困る」時の1行(絞り込み)
@ServiceConnection(type = JdbcConnectionDetails.class) // JDBCだけ作る
依存を入れすぎると候補が増えて混乱しやすいので、「自分はJDBC派?R2DBC派?」を先に決めるのが最短です。
次の一歩(自分のプロジェクトに入れる手順)
- 依存を入れる:
spring-boot-testcontainers+testcontainers(+使うDB/Redisモジュール、使うStarter) - テンプレを貼る:まずはA案(テストクラスの static / companion object)でOKです
- テストを1本だけ動かす:
select 1(Redisならset/get)で“動いた!”を作ったら勝ちです
この3手順でいけたら、あとはエラー早見に戻って詰まりを潰すだけです。
よくある質問
- Q
@ServiceConnectionって結局なにをしてくれるんですか? - A
「このコンテナにSpringをつなげてね」の合図です。コンテナから接続情報(URL/host/port/ユーザー/パスワード)を読んで、Springに自動で入れてくれます。
- Q
@ServiceConnectionを付けたら、コンテナ起動も勝手にやってくれますか? - A
そこは別担当です。起動はTestcontainers(
@Containerなど)、つなぐのが@ServiceConnection です。
- Q
@DynamicPropertySourceはもう不要ですか? - A
対応してるサービスなら、かなり不要になります。ただ、対応外(自作/特殊)は保険として
@DynamicPropertySourceがまだ役立ちます。
- Qどこに
@ServiceConnectionを付ければいいですか? - A
基本は2択です。
このどっちかじゃないと、Springが見つけられず効きません。
- QKotlinだけ動かないんですが、よくある原因は?
- A
だいたい
@JvmField付け忘れです。companion object内のコンテナを “static扱い” にして、Spring/TCが見つけやすくするのがコツです。
- Q「接続先が入ってない(URLがない)」系で落ちます…
- A
多い原因は3つです。
- 依存不足(特に
spring-boot-testcontainers) @ServiceConnectionの 付ける場所ミス(コンテナ本人じゃない)- コンテナが static or @Bean になってない
まずここだけ見直すのが最短です。
- 依存不足(特に
- Qポートって固定じゃないんですか?
localhost:5432に繋がらない… - A
固定じゃないです。コンテナ内部の5432/6379は中の番号で、PC側は毎回別ポートになることがあります。
@ServiceConnectionを使うなら、host/portを決め打ちで書かないのが正解です。
- QDocker Composeを使ってる場合も
@ServiceConnectionは必要ですか? - A
Composeを外で起動してアプリが繋ぐ運用なら、不要な場面が多いです(普通に
spring.datasource.urlなどを設定します)。
テストを自己完結させたいなら、基本は Testcontainers + @ServiceConnection に寄せるのがラクです。
- QCI(GitHub Actionsなど)でだけ落ちます。何を疑えばいいですか?
- A
まずはこれです👇
ローカルOK/CIだけNGは、この3つでだいたい説明つきます。
- QJDBCとR2DBCが両方作られて混乱します。止められますか?
- A
止められます。まず Starter入れすぎを疑って、必要な方だけに減らすのが最優先です。
それでも必要なら、@ServiceConnectionの type属性で「作る接続情報」を絞れます(例:JDBCだけ)。
