The Main Application Class
What @SpringBootApplication switches on, what SpringApplication.run does, and why the main class should stay almost empty.
@SpringBootApplication(exclude = SomeAutoConfiguration.class)
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}Every Boot application has one class with a main method that calls SpringApplication.run. That call creates the ApplicationContext, loads configuration, applies auto-configuration, creates every bean, starts the embedded server and returns the running context.
Three annotations in one
@SpringBootApplication combines:
@SpringBootConfiguration: marks the class as the application's main configuration. Test annotations search upwards for it.@EnableAutoConfiguration: applies Boot's auto-configuration based on the classpath and your beans.@ComponentScan: finds@Component,@Service,@RestControllerand friends in this package and below.
Its attributes tune those parts: exclude switches off specific auto-configurations, and scanBasePackages widens scanning. Needing scanBasePackages usually means the main class is in the wrong package.
Keep it nearly empty
The main class is also what test slices load. @WebMvcTest finds it and applies everything declared on it. Put @EnableScheduling, @EnableJpaRepositories or a set of @Bean methods here, and every slice test inherits them: a web test suddenly wants a database. Move such things into separate @Configuration classes in the relevant feature package, where slices leave them out.
Running code at start-up
Do not put start-up logic in main after run, and avoid long work in constructors. Implement ApplicationRunner (or CommandLineRunner) as a bean: it runs once the context has started, before the application is marked ready, and an exception from it stops start-up with a clear error. For work that should happen after everything is up, listen for ApplicationReadyEvent.
Runners block start-up while they run. A slow one keeps the readiness state at refusing traffic, so in Kubernetes the pod receives no requests and a rollout can time out waiting for it. Long jobs belong in a scheduled or asynchronous task.
Customising SpringApplication
Instead of the static run, you can create a SpringApplication and set options such as the banner mode, lazy initialisation or extra profiles before starting it. Most of these also exist as spring.main.* properties, which are easier to change per environment and should be preferred.
Example
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
public class OrdersApplication {
public static void main(String[] args) {
var app = new SpringApplication(OrdersApplication.class);
app.setBannerMode(Banner.Mode.OFF);
ConfigurableApplicationContext context = app.run(args);
// The returned context is live: the server is listening at this point.
System.out.println("Beans created: " + context.getBeanDefinitionCount());
}
}
When to use it
- Fail start-up early when a required external file or table is missing, using an ApplicationRunner.
- Stop a @WebMvcTest from needing a database by moving @EnableJpaRepositories off the main class.
- Run a service that has a JDBC library on its classpath but deliberately no DataSource.
More examples
Start-up work with an ApplicationRunner
ApplicationArguments parses --name=value options for you, unlike CommandLineRunner's raw String array. If run throws, the application fails to start and the exception is in the log, which is the right outcome for a check the service cannot run without. Keep the work short: start-up waits for it.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class CatalogueCheck implements ApplicationRunner {
private static final Logger log = LoggerFactory.getLogger(CatalogueCheck.class);
@Override
public void run(ApplicationArguments args) {
// --dry-run on the command line shows up as an option name here.
if (args.containsOption("dry-run")) {
log.info("Dry run: skipping catalogue check");
return;
}
log.info("Catalogue check passed");
}
}Reacting once the application is ready
ApplicationReadyEvent is published after all runners have finished, so it is the point where the application is actually serving. Logging the start-up time here gives you a number to watch across releases; a jump usually means a new starter or a slow bean.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class ReadyLogger {
private static final Logger log = LoggerFactory.getLogger(ReadyLogger.class);
@EventListener(ApplicationReadyEvent.class)
public void onReady(ApplicationReadyEvent event) {
log.info("Ready after {} ms", event.getTimeTaken().toMillis());
}
}Switching off one auto-configuration
In Boot 4 DataSourceAutoConfiguration lives in org.springframework.boot.jdbc.autoconfigure, not the Boot 3 package. Excluding by class fails to compile if the class is missing, which is a useful safety net; the spring.autoconfigure.exclude property does the same from configuration, for example only in one profile.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
// The JDBC starter is on the classpath for a library, but this service has no database.
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class ReportingApplication {
public static void main(String[] args) {
SpringApplication.run(ReportingApplication.class, args);
}
}
Discussion