spring-projects / spring-boot

Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.
https://spring.io/projects/spring-boot
Apache License 2.0
75.19k stars 40.69k forks source link

Spring Boot 3 Native Fails to Start with Kotlin @JvmStatic Main Method #32918

Open akefirad opened 2 years ago

akefirad commented 2 years ago

Bug Report for Spring Boot 3 Native (GraalVM 22.3) Having a simple Spring Boot application (generated on start.spring.io), changing the main class to:

@SpringBootApplication(proxyBeanMethods = false)
class DemoApplication {
  companion object {
    @JvmStatic
    fun main(args: Array<String>) {
      runApplication<DemoApplication>(*args)
    }
  }
}

And building the native image (via ./gradlew nativeCompile) gives you an executable that fails to start with:

2022-10-29T11:11:27.459+02:00 ERROR 8553 --- [           main] o.s.boot.SpringApplication               : Application run failed

java.lang.IllegalArgumentException: Could not find class [com.example.demo.DemoApplication$Companion__ApplicationContextInitializer]
        at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:333) ~[na:na]
        at org.springframework.context.aot.AotApplicationContextInitializer.instantiateInitializer(AotApplicationContextInitializer.java:80) ~[demo:6.0.0-RC2]
        at org.springframework.context.aot.AotApplicationContextInitializer.initialize(AotApplicationContextInitializer.java:71) ~[demo:6.0.0-RC2]
        at org.springframework.context.aot.AotApplicationContextInitializer.lambda$forInitializerClasses$0(AotApplicationContextInitializer.java:61) ~[demo:6.0.0-RC2]
        at org.springframework.boot.SpringApplication.applyInitializers(SpringApplication.java:603) ~[demo:3.0.0-RC1]
        at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:383) ~[demo:3.0.0-RC1]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) ~[demo:3.0.0-RC1]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1302) ~[demo:3.0.0-RC1]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1291) ~[demo:3.0.0-RC1]
        at com.example.demo.DemoApplication$Companion.main(DemoApplication.kt:15) ~[na:na]
        at com.example.demo.DemoApplication.main(DemoApplication.kt) ~[demo:na]
Caused by: java.lang.ClassNotFoundException: com.example.demo.DemoApplication$Companion__ApplicationContextInitializer
        at java.base@19.0.1/java.lang.Class.forName(DynamicHub.java:1132) ~[demo:na]
        at org.springframework.util.ClassUtils.forName(ClassUtils.java:283) ~[na:na]
        at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:323) ~[na:na]
        ... 10 common frames omitted

Let me know if you need more information. Thanks.

mhalbritter commented 2 years ago

Looks like we deduce the wrong class name for the application context initializer: com.example.demo.DemoApplication$Companion__ApplicationContextInitializer because of the Kotlin Companion Object. The generated initializer is called DemoApplication__ApplicationContextInitializer.

Workaround until fixed:

@SpringBootApplication
class DemoApplication

fun main(args: Array<String>) {
    runApplication<DemoApplication>(*args)
}

which is the default code the initializer generates.

akefirad commented 2 years ago

You're right. The workaround doesn't work for me since I have to set the main class in bootRun task in build.gradle, unless there's a way to set the main class in the task when the main method is a free one.

mhalbritter commented 2 years ago

There is. The default class name for free functions is the filename + "Kt", so in your case DemoApplicationKt:

springBoot {
    mainClass.set("com.example.demo.DemoApplicationKt")
}

You can rename the class by using

@file:JvmName("SomeOtherClassName")
springBoot {
    mainClass.set("com.example.demo.SomeOtherClassName")
}

See here: https://kotlinlang.org/docs/java-to-kotlin-interop.html#package-level-functions

akefirad commented 2 years ago

Got it. Thanks for the link. 🙏

wilkinsona commented 2 years ago

The workaround doesn't work for me since I have to set the main class in bootRun task

This shouldn't be necessary. bootRun should be automatically configured to use the right main class when using either a companion object or a package-level function.

wilkinsona commented 2 years ago

I wonder if we should set the main class in the Kotlin extension. Things are a little odd at the moment, even without AOT, as the companion object's class is used for logging:

2022-11-03T19:46:16.594Z  INFO 33846 --- [           main] e.k.KotlinMainClassApplication$Companion : Starting KotlinMainClassApplication.Companion using Java 17.0.5 with PID 33846 (/Users/awilkinson/dev/temp/kotlin-main-class/build/classes/kotlin/main started by awilkinson in /Users/awilkinson/dev/temp/kotlin-main-class)

If we set the main class, it looks like this instead:

2022-11-03T19:48:04.743Z  INFO 35059 --- [           main] c.e.k.KotlinMainClassApplication         : Starting KotlinMainClassApplication using Java 17.0.5 with PID 35059 (/Users/awilkinson/dev/temp/kotlin-main-class/build/classes/kotlin/main started by awilkinson in /Users/awilkinson/dev/temp/kotlin-main-class)

That's after making these changes to the extensions:

diff --git a/spring-boot-project/spring-boot/src/main/kotlin/org/springframework/boot/SpringApplicationExtensions.kt b/spring-boot-project/spring-boot/src/main/kotlin/org/springframework/boot/SpringApplicationExtensions.kt
index 8a1269d06b..3ae3cdbc6c 100644
--- a/spring-boot-project/spring-boot/src/main/kotlin/org/springframework/boot/SpringApplicationExtensions.kt
+++ b/spring-boot-project/spring-boot/src/main/kotlin/org/springframework/boot/SpringApplicationExtensions.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012-2017 the original author or authors.
+ * Copyright 2012-2022 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,8 +26,12 @@ import org.springframework.context.ConfigurableApplicationContext
  * @author Sebastien Deleuze
  * @since 2.0.0
  */
-inline fun <reified T : Any> runApplication(vararg args: String): ConfigurableApplicationContext =
-               SpringApplication.run(T::class.java, *args)
+inline fun <reified T : Any> runApplication(vararg args: String): ConfigurableApplicationContext {
+       val application = SpringApplication(T::class.java)
+       application.setMainApplicationClass(T::class.java)
+       return application.run(*args)
+}
+

 /**
  * Top level function acting as a Kotlin shortcut allowing to write
@@ -38,5 +42,8 @@ inline fun <reified T : Any> runApplication(vararg args: String): ConfigurableAp
  * @author Sebastien Deleuze
  * @since 2.0.0
  */
-inline fun <reified T : Any> runApplication(vararg args: String, init: SpringApplication.() -> Unit): ConfigurableApplicationContext =
-               SpringApplication(T::class.java).apply(init).run(*args)
+inline fun <reified T : Any> runApplication(vararg args: String, init: SpringApplication.() -> Unit): ConfigurableApplicationContext {
+       val application = SpringApplication(T::class.java).apply(init)
+       application.setMainApplicationClass(T::class.java)
+       return application.run(*args)
+}

WDYT, @sdeleuze, does this make sense?

wilkinsona commented 2 years ago

Looking more closely, I think this is a bug in the code where we deduce the main application class. It can effect Java too if you write some (convoluted) code that has a similar structure to the code the Kotlin's compiler generates:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        Companion.main(args);
    }

    static class Companion {

        static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);         
        }

    }

}

SpringApplicaton will deduce the main class to be DemoApplication$Companion but the MainClassFinder that's used by our Maven and Gradle plugins will correctly identify that it's DemoApplication.

At the moment we identify the main class by walking the stack and finding the first frame for a method named main. In the example above, we really need to look for the last frame. However, by doing that we risk picking up another main method that's then called the application's main method to start it. @SpringBootTests configured to use the main method would be affected by this were it not for the fact that they explicitly set the main application class.

We can't narrow things down by looking for a particular type of method as it doesn't work with Graal. For the reasons above, I'm also not sure that we can safely look for the last method named main rather than the first. I'm back to thinking that a Kotlin-specific fix may be our best option.

wilkinsona commented 2 years ago

Here's a change that corrects the main class only when a companion object is used:

diff --git a/spring-boot-project/spring-boot/src/main/kotlin/org/springframework/boot/SpringApplicationExtensions.kt b/spring-boot-project/spring-boot/src/main/kotlin/org/springframework/boot/SpringApplicationExtensions.kt
index 8a1269d06b..2405a00f2e 100644
--- a/spring-boot-project/spring-boot/src/main/kotlin/org/springframework/boot/SpringApplicationExtensions.kt
+++ b/spring-boot-project/spring-boot/src/main/kotlin/org/springframework/boot/SpringApplicationExtensions.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012-2017 the original author or authors.
+ * Copyright 2012-2022 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,8 +26,15 @@ import org.springframework.context.ConfigurableApplicationContext
  * @author Sebastien Deleuze
  * @since 2.0.0
  */
-inline fun <reified T : Any> runApplication(vararg args: String): ConfigurableApplicationContext =
-               SpringApplication.run(T::class.java, *args)
+inline fun <reified T : Any> runApplication(vararg args: String): ConfigurableApplicationContext {
+       val application = SpringApplication(T::class.java)
+       val possibleCompanionClass = object{}.javaClass.enclosingClass
+       if (possibleCompanionClass.enclosingClass != null && possibleCompanionClass.kotlin.isCompanion) {
+               application.setMainApplicationClass(possibleCompanionClass.enclosingClass)
+       }
+       return application.run(*args)
+}
+

 /**
  * Top level function acting as a Kotlin shortcut allowing to write
@@ -38,5 +45,11 @@ inline fun <reified T : Any> runApplication(vararg args: String): ConfigurableAp
  * @author Sebastien Deleuze
  * @since 2.0.0
  */
-inline fun <reified T : Any> runApplication(vararg args: String, init: SpringApplication.() -> Unit): ConfigurableApplicationContext =
-               SpringApplication(T::class.java).apply(init).run(*args)
+inline fun <reified T : Any> runApplication(vararg args: String, init: SpringApplication.() -> Unit): ConfigurableApplicationContext {
+       val application = SpringApplication(T::class.java).apply(init)
+       val possibleCompanionClass = object{}.javaClass.enclosingClass
+       if (possibleCompanionClass.enclosingClass != null && possibleCompanionClass.kotlin.isCompanion) {
+               application.setMainApplicationClass(possibleCompanionClass.enclosingClass)
+       }
+       return application.run(*args)
+}

It's pretty gross. It's using an anonymous inner class (object{}) and then getting the enclosing class to identify the class into which the function has been inlined. We can then check if it's a companion class and set the main application class if it is.

alsikorski commented 1 year ago

I had the same problem in Java and found a workaround. Maybe that'll help you somehow.

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        Companion.main(args);
    }

    static class Companion {

        static void main(String[] args) {
                          SpringApplication springApplication = new SpringApplication(DemoApplication.class);
                          springApplication.setMainApplicationClass(DemoApplication.class);
                          springApplication.run(args);      
        }

    }
}
takanoro commented 1 year ago

Have the same problem. No luck with workarounds that were mentioned here.

java.lang.IllegalArgumentException: Could not find class [xyz.app.ApplicationKt__ApplicationContextInitializer]
        at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:333)
        at org.springframework.context.aot.AotApplicationContextInitializer.instantiateInitializer(AotApplicationContextInitializer.java:80)
        at org.springframework.context.aot.AotApplicationContextInitializer.initialize(AotApplicationContextInitializer.java:71)
        at org.springframework.context.aot.AotApplicationContextInitializer.lambda$forInitializerClasses$0(AotApplicationContextInitializer.java:61)
        at org.springframework.boot.SpringApplication.applyInitializers(SpringApplication.java:605)
        at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:385)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:309)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1304)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1293)
        at xyz.app.ApplicationKt.main(Application.kt:13)
Caused by: java.lang.ClassNotFoundException: xyz.app.ApplicationKt__ApplicationContextInitializer
        at java.base@17.0.6/java.lang.Class.forName(DynamicHub.java:1132)
        at org.springframework.util.ClassUtils.forName(ClassUtils.java:283)
        at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:323)

build.gradle.kts

plugins {
    id("org.springframework.boot") version "3.0.5"
    id("io.spring.dependency-management") version "1.0.13.RELEASE" 
    kotlin("jvm") version "1.8.10" 
    kotlin("plugin.spring") version "1.8.10" 
    kotlin("kapt") version "1.8.10"
}
...

tasks.bootBuildImage {
    builder.set("paketobuildpacks/builder:tiny")

    environment.set(
        mapOf(
            "BP_NATIVE_IMAGE" to "true",
            "BP_NATIVE_IMAGE_BUILD_ARGUMENTS" to
                    """
                    --verbose
                    --no-fallback
                    --initialize-at-build-time=org.slf4j.LoggerFactory,ch.qos.logback
                    --trace-class-initialization=ch.qos.logback.classic.Logger 
                    --initialize-at-run-time=io.netty
                """.trimIndent()
        )
    )
}
FloHin commented 1 year ago

We can confirm @takanoro's problem, even tried the approach to rename the kotlin main class accordingly

DemoMain.kt

@file:JvmName("DemoMain")

package at.demo

import org.springframework.boot.runApplication

fun main(args: Array<String>) {
    runApplication<DemoApp>(*args)
}

DemoApp.kt

@EnableScheduling
@SpringBootApplication(
  ...
)
@ConfigurationPropertiesScan
...
class DemoApp

build.gradle:

plugins {
    id 'org.springframework.boot' version '3.1.0'
    id 'io.spring.dependency-management'
    id 'org.asciidoctor.jvm.convert' version "2.4.0"
    id 'org.graalvm.buildtools.native' version '0.9.22'
}

....

springBoot {
    mainClass = 'at.demo.DemoMain'
}

....

bootBuildImage {
    enabled = true
    builder = "paketobuildpacks/builder:tiny"
    imageName = "ghcr.io/demo/demo:native"
    environment = [
            "BP_NATIVE_IMAGE"                : "true",
            "BP_NATIVE_IMAGE_BUILD_ARGUMENTS": "" +
                    "--verbose " +
                    "--no-fallback " +
                    "--enable-https " +
                    "-Djdk.tls.client.protocols=TLSv1.2 " +
                    "--add-opens=java.base/java.time=ALL-UNNAMED " +
                    "--add-opens=java.base/java.nio=ALL-UNNAMED " +
                    "--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED " +
                    "--add-opens=java.base/jdk.internal.ref=ALL-UNNAMED " +
                    "--trace-class-initialization=ch.qos.logback.classic.Logger " +
                    '--trace-object-instantiation=ch.qos.logback.core.AsyncAppenderBase$Worker ' +
                    "--initialize-at-build-time=org.slf4j.LoggerFactory,ch.qos.logback " +
                    "--initialize-at-run-time=io.netty " +
                    "--report-unsupported-elements-at-runtime "
    ]
}

I tried both with 3.0 and 3.1 spring versions. Which finally results in a built image which cannot start its main class:


2023-06-09T12:19:17.190254236Z   .   ____          _            __ _ _
2023-06-09T12:19:17.190256831Z  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
2023-06-09T12:19:17.190258664Z ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
2023-06-09T12:19:17.190260327Z  \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
2023-06-09T12:19:17.190261900Z   '  |____| .__|_| |_|_| |_\__, | / / / /
2023-06-09T12:19:17.190263794Z  =========|_|==============|___/=/_/_/_/
2023-06-09T12:19:17.190265387Z  :: Spring Boot ::                (v3.1.0)
2023-06-09T12:19:17.190267571Z 
2023-06-09T12:19:17.191701237Z 12:19:17.190 [main] ERROR org.springframework.boot.SpringApplication -- Application run failed
2023-06-09T12:19:17.191726434Z java.lang.IllegalArgumentException: Could not find class [at.demo.DemoMain__ApplicationContextInitializer]
2023-06-09T12:19:17.191730531Z  at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:334)
2023-06-09T12:19:17.191734148Z  at org.springframework.context.aot.AotApplicationContextInitializer.instantiateInitializer(AotApplicationContextInitializer.java:80)
2023-06-09T12:19:17.191737675Z  at org.springframework.context.aot.AotApplicationContextInitializer.initialize(AotApplicationContextInitializer.java:71)
2023-06-09T12:19:17.191741051Z  at org.springframework.context.aot.AotApplicationContextInitializer.lambda$forInitializerClasses$0(AotApplicationContextInitializer.java:61)
2023-06-09T12:19:17.191744327Z  at org.springframework.boot.SpringApplication.applyInitializers(SpringApplication.java:606)
2023-06-09T12:19:17.191747433Z  at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:386)
2023-06-09T12:19:17.191764495Z  at org.springframework.boot.SpringApplication.run(SpringApplication.java:310)
2023-06-09T12:19:17.191770025Z  at org.springframework.boot.SpringApplication.run(SpringApplication.java:1305)
2023-06-09T12:19:17.191775255Z  at org.springframework.boot.SpringApplication.run(SpringApplication.java:1294)
2023-06-09T12:19:17.191779933Z  at at.demo.DemoMain.main(DemoMain.kt:11)
2023-06-09T12:19:17.191784502Z Caused by: java.lang.ClassNotFoundException: at.demo.DemoMain__ApplicationContextInitializer
2023-06-09T12:19:17.191788910Z  at java.base@17.0.7/java.lang.Class.forName(DynamicHub.java:1132)
2023-06-09T12:19:17.191793268Z  at org.springframework.util.ClassUtils.forName(ClassUtils.java:284)
2023-06-09T12:19:17.191797226Z  at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:324)
2023-06-09T12:19:17.191800812Z  ... 9 common frames omitted
changhr2013 commented 1 year ago

so is there any temporary solution to this problem?

mhalbritter commented 1 year ago
@SpringBootApplication
class Sb32918Application

fun main(args: Array<String>) {
    runApplication<Sb32918Application>(*args)
}

works for me, try for yourself: sb-32918.zip

Could you please attach a sample for a non-working app?

changhr2013 commented 1 year ago

@mhalbritter my mistake, it missed mainClass because of I configured the native-maven-plugin incorrectly, thanks for your help.

Eng-Fouad commented 1 year ago

This happened when upgrading to spring-boot 3.2.0-M1.

mbogner commented 1 year ago

I think I'm facing the same issue with Spring 3.1.2.

The application compiles but when I run it I get an error.

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.2)

02:12:34.177 [main] ERROR org.springframework.boot.SpringApplication -- Application run failed
java.lang.IllegalArgumentException: Could not find class [dev.mbo.linkshortener2.ApplicationKt__ApplicationContextInitializer]
        at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:334)
        at org.springframework.context.aot.AotApplicationContextInitializer.instantiateInitializer(AotApplicationContextInitializer.java:80)
        at org.springframework.context.aot.AotApplicationContextInitializer.initialize(AotApplicationContextInitializer.java:71)
        at org.springframework.context.aot.AotApplicationContextInitializer.lambda$forInitializerClasses$0(AotApplicationContextInitializer.java:61)
        at org.springframework.boot.SpringApplication.applyInitializers(SpringApplication.java:607)
        at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:387)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:311)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295)
        at dev.mbo.linkshortener2.ApplicationKt.main(Application.kt:29)
Caused by: java.lang.ClassNotFoundException: dev.mbo.linkshortener2.ApplicationKt__ApplicationContextInitializer
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:123)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:87)
        at java.base@17.0.8/java.lang.Class.forName(DynamicHub.java:1322)
        at java.base@17.0.8/java.lang.Class.forName(DynamicHub.java:1311)
        at org.springframework.util.ClassUtils.forName(ClassUtils.java:284)
        at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:324)
        ... 9 common frames omitted

My build.gradle.kts:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    // https://plugins.gradle.org/plugin/org.springframework.boot
    id("org.springframework.boot") version "3.1.2"

    // https://plugins.gradle.org/plugin/io.spring.dependency-management
    id("io.spring.dependency-management") version "1.1.3"

    // https://github.com/graalvm/native-build-tools
    id("org.graalvm.buildtools.native") version "0.9.24"

    // https://plugins.gradle.org/plugin/org.jetbrains.kotlin.jvm
    kotlin("jvm") version "1.9.0"
    // https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.spring
    kotlin("plugin.spring") version "1.9.0"
}

group = "dev.mbo"

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(17))
    }
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}

repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("org.springframework.boot:spring-boot-starter-data-redis-reactive")
    implementation("org.springframework.boot:spring-boot-starter-mail")
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.springframework.boot:spring-boot-starter-validation")
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
    implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")

    // https://mvnrepository.com/artifact/io.netty/netty-resolver-dns-native-macos
    implementation("io.netty:netty-resolver-dns-native-macos:4.1.96.Final:osx-aarch_64")

    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")

    developmentOnly("org.springframework.boot:spring-boot-devtools")

    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.springframework.boot:spring-boot-testcontainers")
    testImplementation("io.projectreactor:reactor-test")
    testImplementation("org.springframework.security:spring-security-test")
    testImplementation("org.testcontainers:junit-jupiter")
}

tasks {
    withType<KotlinCompile> {
        kotlinOptions {
            freeCompilerArgs += "-Xjsr305=strict"
            jvmTarget = "17"
        }
    }

    withType<Test> {
        useJUnitPlatform()
    }

    withType<Copy> {
        duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    }

    bootJar {
        archiveFileName = "${project.name}-all.jar"
        exclude("application-sec*.yml")
    }

    withType<Wrapper> {
        // https://gradle.org/releases/
        gradleVersion = "8.2.1"
        distributionType = Wrapper.DistributionType.ALL
    }

    graalvmNative {
        binaries {
            named("main") {
                mainClass.set("dev.mbo.linkshortener2.ApplicationKt")
            }
        }
    }

}

Without setting the main class like a few lines above the nativeCompile task fails.

My main looks like this:

package dev.mbo.linkshortener2

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class Application

fun main(args: Array<String>) {
    runApplication<Application>(*args)
}

Starting the application from the bootJar works fine. Setting mainClass.set("dev.mbo.linkshortener2.Application") complained about not finding the main method.

mhalbritter commented 1 year ago

Can you share the code with us so I can take a look?

rt-works commented 1 year ago

Having the same issue on Spring Boot 3.1.0, Kotlin , Gradle. I don't use nativeImage plugin, I use buildpacks. I get with bootBuildImage a built docker image, which can't start with the same error:

 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.0)

15:49:31.620 [main] ERROR org.springframework.boot.SpringApplication -- Application run failed
java.lang.IllegalArgumentException: Could not find class [com.***.ManagementApplicationKt__ApplicationContextInitializer]
    at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:334)
    at org.springframework.context.aot.AotApplicationContextInitializer.instantiateInitializer(AotApplicationContextInitializer.java:80)
    at org.springframework.context.aot.AotApplicationContextInitializer.initialize(AotApplicationContextInitializer.java:71)
    at org.springframework.context.aot.AotApplicationContextInitializer.lambda$forInitializerClasses$0(AotApplicationContextInitializer.java:61)
    at org.springframework.boot.SpringApplication.applyInitializers(SpringApplication.java:606)
    at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:386)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:310)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1305)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1294)
    at com.deliveryhero.vms.VendorManagementApplicationKt.main(VendorManagementApplication.kt:33)
Caused by: java.lang.ClassNotFoundException: com.***.ManagementApplicationKt__ApplicationContextInitializer
    at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:123)
    at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:87)
    at java.base@17.0.7/java.lang.Class.forName(DynamicHub.java:1324)
    at java.base@17.0.7/java.lang.Class.forName(DynamicHub.java:1313)
    at org.springframework.util.ClassUtils.forName(ClassUtils.java:284)
    at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:324)
    ... 9 common frames omitted

my root class:

@SpringBootApplication
@EnableConfigurationProperties(
...
)
@EnableAsync
class ***ManagementApplication

fun main(args: Array<String>) {
    runApplication<***ManagementApplication>(*args)
}

This is my gradle.build part related to the graalvm and native image

plugins {
    kotlin("jvm")
    kotlin("plugin.spring")
    id("io.spring.dependency-management")
    id("org.springframework.boot")
    id("org.graalvm.buildtools.native") version "0.9.23"
    id("com.avast.gradle.docker-compose")
    id("com.adarshr.test-logger")
    jacoco
}

tasks.named<BootBuildImage>("bootBuildImage") {
    environment.set(
        environment.get() + mapOf(
            "BP_NATIVE_IMAGE" to "true",
            "BP_NATIVE_IMAGE_BUILD_ARGUMENTS" to "--initialize-at-build-time=org.slf4j.impl.StaticLoggerBinder,org.slf4j.LoggerFactory,ch.qos.logback.classic.Logger,ch.qos.logback.core.spi.AppenderAttachableImpl,ch.qos.logback.core.status.StatusBase,ch.qos.logback.classic.Level,ch.qos.logback.core.status.InfoStatus,ch.qos.logback.classic.PatternLayout,ch.qos.logback.core.CoreConstants,ch.qos.logback.core.util.StatusPrinter,ch.qos.logback.core.util.Loader"
        )
    )
}
mbogner commented 1 year ago

@mhalbritter uploaded to https://github.com/mbogner/link-shortener2

highly work in progress but it runs as jar on my mac M1. didn't work on the dockerisation scripts yet that are included

rt-works commented 1 year ago

I compared my build with a hello-world project generated from start.spring.io. They bootBuildImage task generates this classes for the hello world project like image but it's not generated for my current project.

PS: I also use a multimodule gradle project and build like:

./gradlew -Pversion=v1 :management-service:bootBuildImage --runImage=paketobuildpacks/run:full-cnb

PPS: Somehow after

./gradlew -Pversion=v1 :management-service:bootBuildImage --runImage=paketobuildpacks/run:full-cnb

the folder build/generated with all AOT classes is missing for me

kkocel commented 1 year ago

I tried various workarounds, none worked.

@file:JvmName("MyApp")
package com.example.my

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity

@SpringBootApplication
@EnableWebFluxSecurity
class MyApplication

fun main(args: Array<String>) {
    runApplication<MyApplication>(*args)
}
application {
    mainClass.set("com.example.my.MyApp")
}

When running :nativeRun:

Caused by: java.lang.ClassNotFoundException: com.example.my.MyApp__ApplicationContextInitializer

I tried with 3.1.2 and 3.1.0

EDIT: I gave another shot - I rewrote main in Java and it seems that the same error (ClassNotFoundException: com.example.my.MyApp__ApplicationContextInitializer) occurs. FYI @wilkinsona

kkocel commented 1 year ago

I made a project that reproduces this issue - it has two branches: one with Kotlin main and another with Java main. Both fail with the same error. https://github.com/kkocel/classnotfoundrepero

rt-works commented 1 year ago

@kkocel I played around with your main branch to understand why it happens and noticed that when I only downgrade to Kotlin 1.8.22 your example builds and run without the exception.

kotlin("jvm") version "1.8.22"
kotlin("plugin.spring") version "1.8.22"
docker run --rm -p 8080:8080 docker.io/library/classnotfoundrepero:0.0.1-SNAPSHOT

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.2)

2023-08-22T14:38:01.076Z  INFO 1 --- [           main] c.e.c.ClassnotfoundreperoApplicationKt   : Starting AOT-processed ClassnotfoundreperoApplicationKt using Java 17.0.7 with PID 1 (/workspace/com.example.classnotfoundrepero.ClassnotfoundreperoApplicationKt started by cnb in /workspace)
2023-08-22T14:38:01.076Z  INFO 1 --- [           main] c.e.c.ClassnotfoundreperoApplicationKt   : No active profile set, falling back to 1 default profile: "default"
2023-08-22T14:38:01.116Z  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 1 endpoint(s) beneath base path '/actuator'
2023-08-22T14:38:01.136Z  INFO 1 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port 8080
2023-08-22T14:38:01.137Z  INFO 1 --- [           main] c.e.c.ClassnotfoundreperoApplicationKt   : Started ClassnotfoundreperoApplicationKt in 0.074 seconds (process running for 0.079)
mhalbritter commented 1 year ago

Thanks for analyzing, @rt-works!

kkocel commented 1 year ago

@mhalbritter So is there a chance that this issue will get resolved soon?

rt-works commented 1 year ago

In my case my problem was just solved by upgrading to id("org.graalvm.buildtools.native") version "0.9.24". After the the application could start but there are a lot of other errors to solve in runtime.

kkocel commented 1 year ago

@rt-works do you have a minimal working example? do yo use kotlin 1.9 + native buildtools 0.9.24?

rt-works commented 1 year ago

it works for me with kotlin 1.8.22 + native buildtools 0.9.24. I'll prepare the example

kkocel commented 1 year ago

sorry for the confusion. I thought you managed to run this against Kotlin 1.9.0.

So for now we pin-pointed that Kotlin 1.9.0 is causing this issue.

artemptushkin commented 1 year ago

Any update for Kotlin 1.9.*?

Having a simple project, I have the same issue, I'm on:

# .kts
springBoot {
    mainClass.set("com.iptiq.apptemplate.AppTemplateApplicationKt")
}

graalvmNative {
    binaries {
        named("main") {
            mainClass = "foo.baz.apptemplate.AppTemplateApplicationKt"
            buildArgs(
                "-H:+ReportExceptionStackTraces",
                "-H:EnableURLProtocols=http,https",
                "--initialize-at-run-time=io.netty.handler.ssl.BouncyCastleAlpnSslUtils",
                "--initialize-at-build-time=org.slf4j.impl.StaticLoggerBinder,org.slf4j.LoggerFactory,ch.qos.logback.core.spi.AppenderAttachableImpl,ch.qos.logback.core.status.StatusBase,ch.qos.logback.classic.Level,ch.qos.logback.core.status.InfoStatus,ch.qos.logback.classic.PatternLayout,ch.qos.logback.core.CoreConstants,ch.qos.logback.classic.Logger,ch.qos.logback.core.util.Loader,ch.qos.logback.core.util.StatusPrinter"
            )
        }
   }
}

I do just nativeCompile task and run the binary on M1


same stuff but ./gradlew bootBuildImage -i -s - works, it starts in docker

kkocel commented 11 months ago

I can confirm that this issue no longer occurs as of SB 3.2 + Kotlin 1.9.20.

Main:

package com.example.graalvmrepro

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class GraalvmreproApplication

fun main(args: Array<String>) {
    runApplication<GraalvmreproApplication>(*args)
}

gradle script:

application {
    mainClass.set("com.example.graalvmrepro.GraalvmreproApplicationKt")
}
wilkinsona commented 11 months ago

@kkocel thanks, but, as far as I can tell, the issue as originally reported still occurs with Spring Boot 3.2 and Kotlin 1.9.20. In fact, it's got slightly worse as the error message is no longer as helpful as it was:

> Task :nativeRun FAILED

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.0)

2023-12-04T12:25:16.225Z ERROR 83509 --- [           main] o.s.boot.SpringApplication               : Application run failed

java.lang.IllegalStateException: You are starting the application with AOT mode enabled but AOT processing hasn't happened. Please build your application with enabled AOT processing first, or remove the system property 'spring.aot.enabled' to run the application in regular mode
        at org.springframework.util.Assert.state(Assert.java:76) ~[na:na]
        at org.springframework.boot.SpringApplication.addAotGeneratedInitializerIfNecessary(SpringApplication.java:440) ~[gh-32918:3.2.0]
        at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:396) ~[gh-32918:3.2.0]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:322) ~[gh-32918:3.2.0]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1342) ~[gh-32918:3.2.0]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1331) ~[gh-32918:3.2.0]
        at com.example.gh32918.Gh32918Application$Companion.main(Gh32918Application.kt:15) ~[na:na]
        at com.example.gh32918.Gh32918Application.main(Gh32918Application.kt) ~[gh-32918:na]

I think we should consider refining or reverting https://github.com/spring-projects/spring-boot/pull/38188, particularly while this issue is unresolved.