Search code examples
javajpackage

Packaging via jpackage Could not find or load main class


Using jpackage I can't get a pkg to run on macOS. The installation goes as expected however when I launch the installed application it starts then immediately stops.

Attempting to launch it via CLI and it throws

Error: Could not find or load main class com.example.Application
Caused by: java.lang.ClassNotFoundException: com.example.Application

I've passed the MainClass argument as com.example.Application, and I can see it in the installed bundle under Contents/app/example.jar.

Using the Gradle plugin id "org.panteleyev.jpackageplugin" version "1.3.1" to build the native installer:

def os = org.gradle.internal.os.OperatingSystem.current()
def pkgType = os.windows ? 'msi' : os.linux ? 'deb' : 'pkg'
def inputDir = "$buildDir/input"

task copyDependencies (type: Copy) {
    from configurations.runtimeClasspath
    into inputDir
}

task copyJar (type: Copy) {
    from tasks.jar
    into inputDir
}

jpackage {
    dependsOn clean
    dependsOn bootJar
    dependsOn copyDependencies
    dependsOn copyJar

    type = pkgType

    input = inputDir
    destination = "$buildDir/dist"

    appName = 'Example'
    vendor = 'com.example'

    mainJar = tasks.jar.getArchiveFileName().get()
    mainClass = 'com.example.Application'

    javaOptions = ['-Dfile.encoding=UTF-8']
}

The jar runs just fine when launched via IntelliJ/via CLI.

What else do I need to do here?


Solution

  • Since I'm trying to run a Springboot app, the trick is to not try and launch your main class as you'd typically do but instead to use org.springframework.boot.loader.JarLauncher

    This is more properly explained in a blog post by the Spring team

    So a complete working example with Springboot looks like:

    plugins {
        id 'org.springframework.boot' version '2.6.0-SNAPSHOT'
        id 'io.spring.dependency-management' version '1.0.11.RELEASE'
        id 'org.panteleyev.jpackageplugin' version '1.3.1'
    
        id 'application'
    }
    
    group = 'space.forloop'
    version = '1.0.6'
    
    compileJava.options.encoding = 'UTF-8'
    compileTestJava.options.encoding = 'UTF-8'
    javadoc.options.encoding = 'UTF-8'
    
    
    repositories {
        mavenCentral()
        maven { url 'https://repo.spring.io/milestone' }
        maven { url 'https://repo.spring.io/snapshot' }
    }
    
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-web'
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
        
        compileOnly 'org.projectlombok:lombok'
        annotationProcessor 'org.projectlombok:lombok'
    }
    
    configurations {
        compileOnly {
            extendsFrom annotationProcessor
        }
    }
    
    java {
        toolchain {
            languageVersion = JavaLanguageVersion.of(17)
        }
    }
    
    application {
        mainClass = 'com.example.Application'
        applicationDefaultJvmArgs = ["-Dfile.encoding=UTF-8"]
    }
    
    bootJar {
        manifest {
            attributes 'Implementation-Version': "${project.version}"
            attributes 'Implementation-Title': "${project.name}"
        }
    }
    
    // Not required but useful if you want to configure a little more.
    def os = org.gradle.internal.os.OperatingSystem.current()
    def pkgType = os.windows ? 'msi' : os.linux ? 'deb' : 'pkg'
    
    jpackage {
        dependsOn "bootJar"
    
        type = pkgType
    
        input = "${buildDir}/libs"
        destination = "${buildDir}/dist"
    
        appName = 'Example'
        vendor = 'com.example'
    
        mainJar = bootJar.archiveFileName.get()
        mainClass = "org.springframework.boot.loader.JarLauncher"
    
        javaOptions = ['-Dfile.encoding=UTF-8']
    
        macPackageName = bootJar.archiveBaseName.get()
    }