本記事では、TomcatもSpring Bootも使わない、Java 21内蔵HttpServer + Docker Composeによる「究極にシンプルなAPI検証環境」の作り方をご紹介します
Java 21でさらに強化された「単一ソースファイルプログラム実行機能」を活用すれば、スクリプト言語のような手軽さで、コンテナ上でJava APIを動かすことができます。ファイル共有(ボリュームマウント)と組み合わせれば、ローカルのソースコードを書き換えるだけでコンテナ側の挙動が即座に変わる、最高の実験場が手に入ります。接続確認にはもってこいじゃないですかね。
フォルダ構成
/opt/docker
└ java-api ← 【プロジェクトのルートディレクトリ】(作業の起点となる場所)
├ compose.yaml ← Docker Composeの設定ファイル
└ apps ← Javaソースコードを配置する共有フォルダ
└ sample-api.java
composeファイルの作成
compose.yamlというファイル名で以下作成します。
services:
java-api:
# Java 21 の実行環境(JRE)の公式イメージ
image: eclipse-temurin:21-jdk
container_name: my-java-api
ports:
- "9090:8080" # [手元のPCのポート] : [JAR内のアプリが待ち受けるポート]
volumes:
# 手元の apps フォルダを、コンテナ内の /app フォルダにマウント
- ./apps:/app
working_dir: /app
# コンテナが起動したときに実行するコマンド(JARを起動する)
#command: java -jar sample-api.jar
# Java21の機能で、ソースコードをダイレクトに実行!
command: java sample-api.java
restart: always
コンテナで実行するJavaソースコード(sample-api.java)
今回、コンテナの中で起動させるJavaのソースコードです。Java 21の機能を活かすため、事前コンパイルやJARへのビルドは一切行わず、この未コンパイルの .java ファイルのままコンテナに読み込ませて直接実行します。
import com.sun.net.httpserver.HttpServer;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.time.LocalDateTime;
public class SampleApi {
private static final int PORT = 8080;
public static void main(String[] args) throws Exception {
// サーバインスタンス生成(まだスレッドは起動しない)
HttpServer server = HttpServer.create(new InetSocketAddress(PORT), 0);
// ハンドラ登録
server.createContext("/sample-api3", exchange -> {
String clientIp = exchange.getRemoteAddress().getAddress().getHostAddress();
// アクセスログ(IPアドレス・メソッド・パス)
System.out.printf(
"[%s] %s %s from %s%n",
LocalDateTime.now(),
exchange.getRequestMethod(),
exchange.getRequestURI(),
clientIp
);
String json = String.format(
"{\"status\":\"SUCCESS\",\"time\":\"%s\",\"clientIp\":\"%s\"}",
LocalDateTime.now(),
clientIp
);
byte[] bytes = json.getBytes();
exchange.getResponseHeaders().set("Content-Type", "application/json; charset=UTF-8");
exchange.sendResponseHeaders(200, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
//レスポンスボティをバイト列でソケットに書き込む
os.write(bytes);
}
});
// シャットダウンフックでCtrl+cをキャッチし終了
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Stopping server...");
server.stop(0); // 0 = 即時停止(graceful ではない)
System.out.println("Server stopped.");
}));
// 受付スレッド起動(内部でServerSocket.accept()が呼ばれ待機状態(I/O待ち)となる)
server.start();
System.out.println("Connectivity Test Server started on port " + PORT);
}
}
composeファイルの作成
[root@localhost java-api]# pwd
/opt/docker/java-api
# コンテナの起動
#
[root@localhost java-api]# docker compose up -d
[root@localhost java-api]# docker compose up -d
[+] up 2/2
? Network java-api_default Created 0.3s
? Container my-java-api Started
# コンテナの出力ログ(標準出力)をリアルタイムで監視
#
[root@localhost java-api]# docker compose logs -f
my-java-api | Connectivity Test Server started on port 8080
my-java-api | [2026-06-14T15:26:41.595374469] GET /sample-api3 from 192.168.56.1
my-java-api | [2026-06-14T15:27:03.696797032] GET /sample-api3 from 192.168.56.1
my-java-api | [2026-06-14T15:27:56.336340576] GET /sample-api3 from 192.168.56.1
my-java-api | Stopping server... ←別コンソールからコンテナの停止
my-java-api | Server stopped.
my-java-api exited with code 143
[root@localhost java-api]#
# コンテナの停止
#
[root@localhost java-api]# docker compose down
[+] down 2/2
? Container my-java-api Removed 0.8s
? Network java-api_default Removed 0.4s
[root@localhost java-api]#
今回の記事は以下したものをDockerコンテナ化したものです

最小構成の Java API を作りながら HttpServer の内部を理解する
Tomcat や Jetty といったサーブレットコンテナは、Servlet API(HttpServlet / doGet / doPost)を使って HTTP リクエストを処理します。しかし今回は、Servlet API を使わず、JD…

