Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
akefirad opened this issue Oct 29, 2022 · 32 comments
Open

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

akefirad opened this issue Oct 29, 2022 · 32 comments
Labels
theme: aot An issue related to Ahead-of-time processing theme: kotlin type: bug A general bug
Milestone

Comments

@akefirad
Copy link

akefirad commented Oct 29, 2022

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 [email protected]/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.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Oct 29, 2022
@mhalbritter
Copy link
Contributor

mhalbritter commented Oct 31, 2022

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.

@mhalbritter mhalbritter added type: bug A general bug theme: aot An issue related to Ahead-of-time processing and removed status: waiting-for-triage An issue we've not yet triaged labels Oct 31, 2022
@mhalbritter mhalbritter added this to the 3.0.x milestone Oct 31, 2022
@akefirad
Copy link
Author

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
Copy link
Contributor

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
Copy link
Author

Got it. Thanks for the link. 🙏

@wilkinsona
Copy link
Member

wilkinsona commented Nov 1, 2022

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
Copy link
Member

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
Copy link
Member

wilkinsona commented Nov 3, 2022

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
Copy link
Member

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.

@philwebb philwebb added the for: team-meeting An issue we'd like to discuss as a team to make progress label Nov 4, 2022
@philwebb philwebb removed the for: team-meeting An issue we'd like to discuss as a team to make progress label Nov 14, 2022
@alsikorski
Copy link

alsikorski commented Dec 21, 2022

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
Copy link

takanoro commented Mar 25, 2023

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 [email protected]/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
Copy link

FloHin commented Jun 9, 2023

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 [email protected]/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
Copy link

so is there any temporary solution to this problem?

@mhalbritter
Copy link
Contributor

mhalbritter commented Jun 14, 2023

@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
Copy link

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

@Eng-Fouad
Copy link
Contributor

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

@mbogner
Copy link

mbogner commented Aug 17, 2023

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 [email protected]/java.lang.Class.forName(DynamicHub.java:1322)
        at [email protected]/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
Copy link
Contributor

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

@rt-works
Copy link

rt-works commented Aug 17, 2023

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 [email protected]/java.lang.Class.forName(DynamicHub.java:1324)
	at [email protected]/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
Copy link

mbogner commented Aug 17, 2023

@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
Copy link

rt-works commented Aug 17, 2023

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
Copy link

kkocel commented Aug 21, 2023

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
Copy link

kkocel commented Aug 22, 2023

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
Copy link

rt-works commented Aug 22, 2023

@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
Copy link
Contributor

Thanks for analyzing, @rt-works!

@kkocel
Copy link

kkocel commented Aug 22, 2023

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

@rt-works
Copy link

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
Copy link

kkocel commented Aug 23, 2023

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

@rt-works
Copy link

rt-works commented Aug 23, 2023

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

@kkocel
Copy link

kkocel commented Aug 23, 2023

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
Copy link

artemptushkin commented Oct 16, 2023

Any update for Kotlin 1.9.*?

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

  • org.graalvm.buildtools.native: 0.9.27
  • spring-boot 3.1.4
  • gradle 8.4
  • JDK 17 GraalVM
  • Kotlin 1.9.10
# .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

@philwebb philwebb modified the milestones: 3.0.x, 3.1.x Nov 8, 2023
@kkocel
Copy link

kkocel commented Nov 27, 2023

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
Copy link
Member

@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 #38188, particularly while this issue is unresolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
theme: aot An issue related to Ahead-of-time processing theme: kotlin type: bug A general bug
Projects
None yet
Development

No branches or pull requests