Gradle构建Java应用部署Heroku失败:Web进程无法绑定$PORT
Heroku部署Java Jooby应用启动超时(R10错误)排查与解决
问题现象
使用Gradle部署Java Jooby应用到Heroku后,执行heroku open页面长时间空白,Heroku日志返回R10启动超时错误。
错误日志
2022-10-10T05:52:17.000000+00:00 app[api]: Build succeeded 2022-10-10T05:53:40.640805+00:00 heroku[web.1]: Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 90 seconds of launch 2022-10-10T05:53:40.718527+00:00 heroku[web.1]: Stopping process with SIGKILL 2022-10-10T05:53:40.842261+00:00 heroku[web.1]: Process exited with status 137 2022-10-10T05:53:40.890653+00:00 heroku[web.1]: State changed from starting to crashed
当前配置与代码
Procfile
web: java $JAVA_OPTS -Dserver.port=$PORT -jar app/build/libs/app-all.jar
build.gradle
/* * This file was generated by the Gradle 'init' task. * * This generated file contains a sample Java application project to get you started. * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle * User Manual available at https://docs.gradle.org/7.4/userguide/building_java_projects.html */ plugins { // Apply the application plugin to add support for building a CLI application in Java. id 'application' } jar { manifest { attributes( 'Main-Class': 'group5MentorMatcher.Main', ) } } task fatJar(type: Jar) { duplicatesStrategy = DuplicatesStrategy.EXCLUDE manifest.from jar.manifest classifier = 'all' from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } { exclude "META-INF/*.SF" exclude "META-INF/*.DSA" exclude "META-INF/*.RSA" exclude "META-INF/*.TXT" } with jar } repositories { // Use Maven Central for resolving dependencies. mavenCentral() } dependencies { //Dependencies for testing def junitVer = "5.8.2"; testImplementation group: "org.junit.jupiter", name: "junit-jupiter-api", version: junitVer; testRuntimeOnly group: "org.junit.jupiter", name: "junit-jupiter-engine", version: junitVer; testImplementation group: "org.hamcrest", name: "hamcrest", version: "2.2"; // Use JUnit Jupiter for testing. testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1' // This dependency is used by the application. implementation 'com.google.guava:guava:30.1.1-jre' // Use postgres to connect to the database implementation 'org.postgresql:postgresql:42.4.0' // guava implementation group: 'com.google.guava', name: 'guava', version: '30.1.1-jre' // jooby def joobyVer = '2.13.0' implementation group: 'io.jooby', name: 'jooby-netty', version: joobyVer implementation group: 'io.jooby', name: 'jooby-gson', version: joobyVer implementation group: 'io.jooby', name: 'jooby-swagger-ui', version: joobyVer //logging implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.36' implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.10' implementation group: 'com.zaxxer', name: 'HikariCP', version: '4.0.3' implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.5' def jdbiVer = '3.21.0' implementation group: 'org.jdbi', name: 'jdbi3-core', version: jdbiVer implementation group: 'org.jdbi', name: 'jdbi3-sqlobject', version: jdbiVer } application { // Define the main class for the application. mainClass = 'group5MentorMatcher.Main' } task stage(dependsOn: ['build', 'clean']) build.mustRunAfter clean task copyToLib(type: Copy) { from configurations.compileClasspath into 'build/libs' } stage.dependsOn(copyToLib) gradle.taskGraph.whenReady { taskGraph -> if (taskGraph.hasTask(stage)) { test.enabled = false } } artifacts { archives fatJar } sourceSets { 'static' { resources { srcDirs = ['static'] } java{ srcDirs = [] } } }
Main类核心代码
public class Main extends Jooby { private final PersonDAO personDAO = JDBIDaoFactory.getPersonDAO(); private final MentorDAO mentorDAO = JDBIDaoFactory.getMentorDAO(); private final MenteeDAO menteeDAO = JDBIDaoFactory.getMenteeDAO(); private final MatchDAO matchDAO = JDBIDaoFactory.getMatchDAO(); private final UndergraduateExperienceDAO undergraduateDAO = JDBIDaoFactory.getUndergraduateExperienceDAO(); private final PostgraduateExperienceDAO postgraduateDAO = JDBIDaoFactory.getPostgraduateExperienceDAO(); private final PostgradCourseDAO pgCourseDAO = JDBIDaoFactory.getPostgradCourseDAO(); private final UndergradCourseDAO ugCourseDAO = JDBIDaoFactory.getUndergradCourseDAO(); public Main() { setServerOptions(new ServerOptions().setPort(8072)); install(new GsonModule()); install(new OpenAPIModule()); assets("/openapi.json", "mentor-matcher.json"); assets("/openapi.yaml", "mentor-matcher.yaml"); assets("/*", Paths.get("app/static")); mount(new PersonModule(personDAO)); mount(new MentorModule(mentorDAO)); mount(new MenteeModule(menteeDAO)); mount(new MatchModule(matchDAO)); mount(new UndergraduateExperienceModule(undergraduateDAO)); mount(new PostgraduateExperienceModule(postgraduateDAO)); mount(new PostgradCourseModule(pgCourseDAO)); mount(new UndergradCourseModule(ugCourseDAO)); } }
解决方案
核心问题
Heroku要求Web进程必须绑定到其动态分配的$PORT环境变量指定的端口,但你的Main类硬编码了端口8072,完全忽略了Procfile中传入的-Dserver.port=$PORT参数,导致应用无法绑定到正确端口,触发R10超时错误。
修改步骤
修改Main类的端口配置逻辑,读取系统属性(对应-Dserver.port=$PORT),同时保留本地开发默认端口:
public class Main extends Jooby { // DAO实例初始化... public Main() { // 读取系统属性中的server.port,默认值8072用于本地开发 int port = Integer.parseInt(System.getProperty("server.port", "8072")); setServerOptions(new ServerOptions().setPort(port)); // 模块安装与路由挂载... } // 补充启动入口方法(Jooby应用必需) public static void main(String[] args) { runApp(args, Main::new); } }
验证流程
- 提交修改:
git add . && git commit -m "fix: use heroku dynamic port" && git push heroku main - 查看部署日志:
heroku logs --tail,确认应用成功绑定到Heroku分配的端口 - 重新访问应用:
heroku open
内容的提问来源于stack exchange,提问作者othnuk
相关产品推荐
相关产品推荐

