はじめに (対象読者・この記事でわかること)
この記事は、JavaでWebアプリケーション開発を行っているエンジニア、特にGradleをビルドツールとして利用し、Herokuへデプロイしようとしている方を対象としています。
- Gradleで作成したServletアプリをHerokuにデプロイした際に、起動時に「java.lang.NoClassDefFoundError」や「Port binding error」などのエラーが出る原因が分かります。
- エラーログの読み方から、Procfile の書き方、system.properties の設定、そして依存ライブラリのスコープ調整まで、実際に動作する形にする具体的な手順が身につきます。
背景として、Heroku の無料プランでも簡単に Java アプリを試したいというニーズが増えている一方で、サーバレス環境特有の設定ミスが頻発している点があります。本記事はその壁を乗り越えるための実践ガイドです。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。
- Java の基本文法と Servlet の概念
- Gradle のビルドスクリプト(build.gradle)の書き方
- Heroku の基本的な概念(Dyno、Procfile、環境変数)
Herokuデプロイの全体像とよくある問題点
Heroku は「git push だけでアプリが動く」ことが魅力ですが、Java アプリの場合は「JDK バージョン指定」や「ポートの取得」など、いくつかの必須設定があります。
1. JDK バージョン
デフォルトでは Heroku の Java Buildpack が使用する JDK が決まっており、ローカルと異なるとコンパイルエラーになることがあります。
2. ポート取得
Heroku は動的に割り当てたポート ($PORT 環境変数) でアプリを待ち受ける必要があります。Servlet コンテナ(Tomcat 等)にこのポートを渡さないと「address already in use」エラーが発生します。
3. 依存ライブラリのスコープ
provided スコープで宣言したライブラリはビルド時にだけ利用され、実行時には含まれません。Heroku のスラッグに含める必要があるものは runtime または compile に変更しないと ClassNotFoundException が起きます。
これらのポイントを踏まえて、実際のデプロイ手順とエラー対策を見ていきます。
実践手順:Gradle+Servlet アプリを Heroku にデプロイしエラーを解消する
以下では、ローカルで gradle build が通っている前提で、Heroku にデプロイするまでの一連の作業をステップごとに解説します。エラーが出たケースを中心に、原因と対策を具体的に示します。
ステップ1:プロジェクト構成と build.gradle の見直し
Groovyplugins { id 'java' id 'war' // Servlet 用に war パッケージを生成 id 'org.gretty' version '4.0.3' // 開発時の組み込みサーバ (オプション) } group = 'com.example' version = '1.0.0' repositories { mavenCentral() } dependencies { // サーブレット API は provided にしておく (Heroku の dyno では自前で提供しない) providedCompile 'jakarta.servlet:jakarta.servlet-api:5.0.0' // 本番でも必要なライブラリは compile または implementation に implementation 'org.springframework.boot:spring-boot-starter-web:3.1.0' // テスト用 testImplementation 'junit:junit:4.13.2' } // War の作成時に依存ライブラリを埋め込む設定 war { from configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } // Heroku 用に JDK バージョンを明示 task generateSystemProperties { doLast { file('system.properties').text = 'java.runtime.version=17' } } processResources.dependsOn generateSystemProperties
providedCompileを使用している理由は、Servlet コンテナ側で提供されるためです。ただし、Heroku の Dyno には Tomcat が自動で組み込まれないので、最終的にwarにすべての依存を入れるか、runtimeに変更します。warタスクでruntimeClasspathをwarに取り込む設定を入れると、ClassNotFoundExceptionを防げます。
ステップ2:Procfile とポート取得コードの追加
Heroku ではアプリが起動するコマンドを Procfile に記述します。Servlet アプリの場合は、組み込みサーバ(例:Jetty、Tomcat)を直接起動させます。
Procfile
web: java -Dserver.port=$PORT -jar build/libs/yourapp-1.0.0.war
-Dserver.port=$PORTで Heroku が割り当てたポートをサーバに渡す。Spring Boot であれば自動的に取得できるが、純粋な Servlet の場合は自前でSystem.getenv("PORT")を取得しConnectorに設定するコードが必要です。
サンプルコード(Embedded Jetty)
Javapublic class Main { public static void main(String[] args) throws Exception { int port = Integer.parseInt(Optional.ofNullable(System.getenv("PORT")).orElse("8080")); Server server = new Server(port); WebAppContext ctx = new WebAppContext(); ctx.setContextPath("/"); ctx.setWar("src/main/webapp"); server.setHandler(ctx); server.start(); server.join(); } }
このコードを src/main/java/com/example/Main.java に追加し、war に含めます。
ステップ3:Heroku アプリ作成とデプロイ
Bash# Heroku CLI がインストールされている前提 heroku create java-servlet-demo --buildpack heroku/java # Git リポジトリが未初期化なら git init git add . git commit -m "Initial commit" # デプロイ git push heroku master
デプロイ中に以下のようなビルドログが出ます。
-----> Java app detected
-----> Installing JDK 17... done
-----> Installing Maven... done
-----> Installing Gradle... done
-----> Compiling and packaging...
ハマった点やエラー解決
エラー1:java.lang.NoClassDefFoundError: jakarta/servlet/http/HttpServletRequest
- 原因:
providedCompileにしたjakarta.servlet-apiが実行時に含まれていない。 - 対策:
runtimeOnlyに変更するか、war { from ... }でライブラリを埋め込む。
GroovyruntimeOnly 'jakarta.servlet:jakarta.servlet-api:5.0.0'
エラー2:Address already in use (Port binding error)
- 原因:サーバが固定ポート
8080でリッスンしようとしている。Heroku では$PORTが必須。 - 対策:コード内で
System.getenv("PORT")を取得し、サーバに設定。Procfileでも-Dserver.port=$PORTを付与。
エラー3:Failed to download JDK version
- 原因:
system.propertiesが無い、またはバージョン指定が古い。 - 対策:
system.propertiesにjava.runtime.version=17を明示。Gradle タスクgenerateSystemPropertiesで自動生成すると便利。
エラー4:Gradle task 'bootRun' not found
- 原因:Spring Boot プラグインが入っていないのに
bootRunを実行しようとした。 - 対策:Spring Boot であれば
plugins { id 'org.springframework.boot' version '3.1.0' }を追加。Servlet 単体ならwarタスクに集中すれば OK。
解決策のまとめ
- 依存ライブラリのスコープを見直す
-providedCompile→runtimeOnlyまたはimplementationに変更し、warに必ず同梱。 - ポート取得ロジックを実装
-System.getenv("PORT")を取得し、サーバ起動時に設定。 system.propertiesで JDK バージョンを固定
-java.runtime.version=17を明示し、Heroku のビルドエラーを防止。Procfileの記述を正しく
-web: java -Dserver.port=$PORT -jar build/libs/yourapp-1.0.0.warの形式で、Dyno が自動的に web プロセスとして起動できるようにする。
以上の手順を踏めば、Gradle でビルドした Java Servlet アプリを Heroku に問題なくデプロイでき、実行時エラーを回避できます。
まとめ
本記事では、Gradle で構築した Java Servlet アプリを Heroku にデプロイした際に直面しやすい「依存ライブラリの欠如」「ポート取得失敗」「JDK バージョン不一致」などのエラーを具体例とともに解説し、以下のポイントで解決する手順を示しました。
- 依存ライブラリは実行時に必ず含める(スコープ変更と
warへの埋め込み) - Heroku が割り当てるポートを取得してサーバに設定
system.propertiesで JDK バージョンを明示- 正しい
Procfile記述で Dyno が起動できるように
これにより、読者は Heroku に Java Servlet アプリをスムーズに展開でき、環境依存のトラブルを最小限に抑えることができます。次回は、データベース接続や環境変数を活用した本格的な Web アプリの構築方法について取り上げる予定です。
参考資料
- Heroku Java Buildpack 公式ドキュメント
- Gradle Official Documentation – War Plugin
- Jakarta Servlet API – Maven Central
- Embedded Jetty Example – Official Guide
