Java 21 introduces (as a preview) support for instance main methods and for main methods (static or instance) that have no parameters:
Selecting a main method
When launching a class, the launch protocol chooses the first of the following methods to invoke: - A
static void main(String[] args)method of non-private access (i.e., public, protected or package) declared in the launched class, - Astatic void main()method of non-private access declared in the launched class, - Avoid main(String[] args)instance method of non-private access declared in the launched class or inherited from a superclass, or, finally, - Avoid main()instance method of non-private access declared in the launched class or inherited from a superclass.
To support these new main methods, some changes will be required in Boot:
MainClassFinder, as used by our Maven and Gradle plugins (and Antlib), only looks forpublic static void main(String[] args)methods at the momentMainMethodRunner, as used by our launchers, only supports running astatic main(String[])method at the moment- DevTools ( see
MainMethod), only supportsstatic main(String[])methods when finding the method to use for restart at the moment
Comment From: rafaelrc7
Hello, is this issue available for work?
Comment From: wilkinsona
Thanks for the offer, @rafaelrc7, but not yet. We haven't decided when to schedule this. As the support is only in preview at the moment, we may wait for it to be delivered in its final form before doing anything.
Comment From: wilkinsona
https://openjdk.org/jeps/512 is tracking the JDK change where they currently expect it to be finalised in Java 25 which should GA in September. If this comes to pass, we could consider this in 4.0.x which will GA in November.
Comment From: joshlong
I feel like the issue's a bit more involved, because you don't even need to define a class.
void main(){
IO.println("hi");
}
is a valid java program.
what would we even pass to SpringApplication.run(Class<T>, args)?
Maybe we could support something like the following, and somehow determine that this is a special implicit class and treat it as though it were annotated with @SpringBootApplication
void main(){
SpringApplication.run(this);
}
or
void main(String args[]){
SpringApplication.run(this,args);
}
also, what about the class path? in the groovy support for spring run, we had @Grab. in java, having a pom.xml and a whole src/{test,main}/{resources,java} tree feels like it'd defeat the intrinsic lightweight script-y feel enabled by this feature.
I wish start.spring.io had an option to generate a run.{sh,bat} that somehow downloaded the right dependencies and put them on the class path if they weren't already there, like the light jars @dsyer built some years ago.
so the deliverable would just be run.sh and a Main.java. or, maybe
Comment From: joshlong
whatever work we did would also help with using Kotlin scripts and spring boot, too
Comment From: vulinh64
JDK 25 is now generally available. Is there any effort to implement this, or we wait for Spring Boot 4?
We may not need to strip down the old main to just void main(String[] args), but to something like this?
@SpringBootApplication
// other metadata annotations
class MainApp {
void main(String[] args) {
SpringBootApplication.run(getClass(), args);
}
}
I can see that Spring Boot's main class still needs a class for its annotations
Comment From: wilkinsona
The method will (probably) have to remain static otherwise you'll end up with two instances of MainApp, one created by the launcher and one created by the application context. The former would be awkward as no dependency injection would occur so you could, in theory, try to use instance fields in the main method that would always be null.
We consider this to be enhancement. As such, it's currently scheduled for 4.0.x as we generally do not include enhancements in maintenance releases.
Comment From: vulinh64
The method will (probably) have to remain static otherwise you'll end up with two instances of
MainApp, one created by the launcher and one created by the application context. The former would be awkward as no dependency injection would occur so you could, in theory, try to use instance fields in the main method that would always benull.We consider this to be enhancement. As such, it's currently scheduled for 4.0.x as we generally do not include enhancements in maintenance releases.
Yep, that's awkward. I've tested with the non-static main and it worked fine in IDE (IntelliJ), but failed spectacularly when dockerizing the app.
This is what I got when dockerizing the app with non-static main method:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "<parameter1>" is null
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.checkReceiver(Unknown Source)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:102)
at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:64)
at org.springframework.boot.loader.launch.JarLauncher.main(JarLauncher.java:40)
JDK 25 also allowed the main method to no longer be public, something like this (it worked):
@SpringBootApplication
class MainApp {
static void main(String[] args) {
SpringApplication.run(MainApp.class, args);
}
}
Maybe one day we can have non-static version? 😁
Comment From: dsyer
This still works (call it demo.sh and make it executable):
///usr/bin/env jbang "$0" "$@" ; exit $?
//SOURCES script@scratches
//JAVA 25
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class Demo {
@Bean
CommandLineRunner runner() {
return args -> System.out.println("Hello, World!");
}
}
void main(String[] args) {
SpringScript.run(args);
}
You can also make it work with Java 17 using JShell (call it demo.jsh and make it executable):
///usr/bin/env jbang "$0" "$@" ; exit $?
//SOURCES springbom@scratches
//SOURCES generic@scratches
//JAVA 17
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration class Demo {
@Bean
CommandLineRunner runner() {
return args -> System.out.println("Hello, World!");
}
}
new SpringApplication(GenericApplication.class, Demo.class).run(args);
(Note that the type-level annotations have to be on the same line as the type definition for JShell.)
My favourite is the one where you run a Markdown script jbang demo.md (it extracts all the "java" fragments and makes them into a .jsh script like the one above). Great for self-documenting scripts.
Comment From: wilkinsona
and it worked fine in IDE (IntelliJ), but failed spectacularly when dockerizing the app
That it works fine in an IDE but then fails when Dockerizing the app (or, presumably, when running it as a jar) is rather awkward. I hadn't considered that angle before and it's the sort of thing that may convince us to consider this to be a bug that could be addressed in 3.5.x.
Comment From: vulinh64
and it worked fine in IDE (IntelliJ), but failed spectacularly when dockerizing the app
That it works fine in an IDE but then fails when Dockerizing the app (or, presumably, when running it as a jar) is rather awkward. I hadn't considered that angle before and it's the sort of thing that may convince us to consider this to be a bug that could be addressed in 3.5.x.
Do you need me to show the error?
Comment From: wilkinsona
Yes, please. It certainly wouldn't do any harm to know exactly how it fails at the moment.
Comment From: vulinh64
Yes, please. It certainly wouldn't do any harm to know exactly how it fails at the moment.
For the full context, this is my repository: https://github.com/vulinh64/spring-base
Dockerfile: https://github.com/vulinh64/spring-base/blob/main/Dockerfile
Docker compose: https://github.com/vulinh64/spring-base/blob/main/docker-compose.yml
pom.xml: https://github.com/vulinh64/spring-base/blob/main/pom.xml
Currently, the (working) main class is as such:
@SpringBootApplication
@EnableJpaAuditing(auditorAwareRef = AuditorConfiguration.AUDITOR_PROVIDER)
@EnableSpringDataWebSupport(pageSerializationMode = PageSerializationMode.VIA_DTO)
@EnableScheduling
@EnableConfigurationProperties(ApplicationProperties.class)
@EnableCaching
class SpringBaseProjectApplication {
static void main(String[] args) {
SpringApplication.run(SpringBaseProjectApplication.class, args);
}
}
When change the main method to not use static like this:
@SpringBootApplication
@EnableJpaAuditing(auditorAwareRef = AuditorConfiguration.AUDITOR_PROVIDER)
@EnableSpringDataWebSupport(pageSerializationMode = PageSerializationMode.VIA_DTO)
@EnableScheduling
@EnableConfigurationProperties(ApplicationProperties.class)
@EnableCaching
class Application {
void main(String[] args) {
SpringApplication.run(getClass(), args);
}
}
And then run run-docker.cmd, after starting the container, this is what I got:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "<parameter1>" is null
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.checkReceiver(Unknown Source)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:102)
at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:64)
at org.springframework.boot.loader.launch.JarLauncher.main(JarLauncher.java:40)
Comment From: wilkinsona
So it already works with a package-private static main method. In that case, we may not do anything here as I am not convinced that instance main methods make sense in the context of a Spring Boot application. We should still consider parameterless main methods though.
Comment From: vulinh64
So it already works with a package-private static main method. In that case, we may not do anything here as I am not convinced that instance main methods make sense in the context of a Spring Boot application. We should still consider parameterless main methods though.
Will those new main() method be in 4.0.x milestone?
Comment From: wilkinsona
That's the current hope. We'll see if we have time.
Comment From: wilkinsona