Have you ever seen the red APPLICATION FAILED TO START text and felt lost about where to start? Spring Boot startup failure errors look scary at first glance, but they actually follow fairly predictable patterns. This article, assuming Spring Boot 3.x / Java 17+, walks you through how to go from logs to resolution while categorizing the causes.

Note that “the app starts but is slow” is a separate issue. For that, see Spring Boot Startup Performance Tuning.

Getting the big picture of startup failures

When Spring Boot fails to start, the FailureAnalyzer first prints a nicely formatted, easy-to-read message. Below that, a stack trace follows—a two-tier structure.

The causes you commonly encounter boil down to roughly these seven:

  • Port conflicts
  • Duplicate or ambiguous Bean definitions
  • Circular references
  • AutoConfiguration failures
  • Missing DataSource configuration
  • @ConfigurationProperties validation failures
  • Profile configuration mistakes

The basic triage flow is: formatted message → stack trace → --debug output, in that order. In most cases, the initial formatted message alone tells you the cause.

How to read FailureAnalyzer messages

The block printed by FailureAnalyzer is divided into a Description section and an Action section.

***************************
APPLICATION FAILED TO START
***************************

Description:

Web server failed to start. Port 8080 was already in use.

Action:

Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.

Description shows “what happened,” and Action shows “what to do next.” The Action instructions often map directly to the fastest path to resolution, so read this first without overthinking.

For errors that FailureAnalyzer doesn’t support, the formatted message won’t appear—you’ll get just the stack trace. In that case, the trick is to walk through Caused by: from the bottom up and find the first business class or configuration file name that appears.

Pulling out more information with the —debug flag

When the cause isn’t visible, enable verbose logs. For an executable JAR, you can pass --debug directly.

java -jar app.jar --debug

When launching via Maven or Gradle, how the flag is propagated varies by version, so the safest options are putting it in application.properties or specifying a JVM property.

# application.properties
debug=true
# Specifying via JVM system property
./mvnw spring-boot:run -Dspring-boot.run.jvmArguments="-Ddebug"
./gradlew bootRun --args='--debug'

Then a CONDITIONS EVALUATION REPORT appears in the startup logs. Positive matches lists the AutoConfigurations that were applied, and Negative matches lists the reasons others were not applied—making it easier to find why auto-configuration isn’t kicking in as expected. The mechanism is explained in detail in How Spring Boot AutoConfiguration works.

Port conflicts

The most common one you’ll hit is a port conflict. The cause is simple: another process is already using 8080.

# macOS / Linux
lsof -i:8080

# Windows
netstat -ano | findstr 8080

A previous Spring Boot app might still be running, or a Docker container might be using the port—both happen often. If you can stop the offending process, that resolves it.

If you really can’t stop it, just change the port in application.properties. For tests, specifying 0 to get a random port is handy.

server.port=8081
# For tests, server.port=0 assigns randomly

Duplicate or ambiguous Bean definitions

The next ones you’ll often see are BeanDefinitionOverrideException and NoUniqueBeanDefinitionException. The names are similar and easy to confuse, but the causes differ.

  • BeanDefinitionOverrideException occurs when two or more Beans are registered with the same name
  • NoUniqueBeanDefinitionException occurs when there are multiple Beans of the same type and the injection point can’t narrow it down to one

The latter can be handled with @Primary or @Qualifier.

@Service
public class OrderService {

    private final PaymentGateway gateway;

    public OrderService(@Qualifier("stripeGateway") PaymentGateway gateway) {
        this.gateway = gateway;
    }
}

For the former, you can suppress it with spring.main.allow-bean-definition-overriding=true, but that’s a band-aid. Starting with Spring Boot 2.1, the default was changed to false specifically to prevent unintended overrides, and enabling it carries the risk of Beans being silently swapped out by whichever loads last. The root cause is usually overlapping component scan ranges, so revisiting the design is safer.

Circular references

Starting with Spring Boot 2.6, circular references are forbidden by default. A state where A depends on B and B depends on A will throw an error at startup.

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  serviceA defined in file [...]
↑     ↓
|  serviceB defined in file [...]
└─────┘

This dependency map appears in the log, so the cycle is obvious at a glance. There are three main ways to handle it.

The first is to switch to setter injection. Constructor injection can’t initialize when there’s a cycle, but setter injection resolves dependencies after Bean creation.

@Service
public class ServiceA {

    private ServiceB serviceB;

    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

The second is to attach @Lazy to one side and resolve via a proxy lazily.

@Service
public class ServiceA {

    private final ServiceB serviceB;

    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

The third is to revisit the design and separate responsibilities; extracting common logic into a third service breaks the cycle. Long-term, this is the healthiest option.

If you really need a quick fix, you can re-allow circular references with spring.main.allow-circular-references=true, but it has the side effect of making initialization order harder to follow, so treat it as a temporary workaround. For Bean-related topics, the Bean Lifecycle article is also useful.

AutoConfiguration not working as expected

When you think “I added the dependency but auto-configuration isn’t kicking in,” check the Negative matches in the --debug output.

HibernateJpaAutoConfiguration:
   Did not match:
      - @ConditionalOnClass did not find required class
        'org.hibernate.SessionFactory' (OnClassCondition)

In this example, you can infer that the equivalent of spring-boot-starter-data-jpa is missing from the dependencies. It tells you why conditions weren’t met—“this class wasn’t found,” “this Bean already exists,” and so on—making it easier to spot missing starter dependencies or version mismatches.

Conversely, when you want to intentionally disable a specific AutoConfiguration, use exclude.

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Missing DataSource configuration

This appears when the DataSource class is on the classpath but no connection URL is specified and no embedded DB driver like H2 can be found.

Failed to configure a DataSource: 'url' attribute is not specified
and no embedded datasource could be configured.

You hit this when you add spring-jdbc or spring-boot-starter-data-jpa but forget to add the JDBC driver, or when production uses PostgreSQL but you haven’t written application.properties for the dev environment.

If you do use a DB, this minimal configuration is enough:

spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=app
spring.datasource.password=secret

Conversely, if this app doesn’t use a DB (the dependency is in only because of a library), exclude DataSourceAutoConfiguration using the exclude shown earlier. For detailed connection pool configuration, see the HikariCP Tuning Guide.

@ConfigurationProperties validation failures

Startup also halts when configuration value types don’t match, or constraints attached via @Validated are violated.

Binding to target ... failed:
   Property: app.maxRetries
   Value: "abc"
   Reason: failed to convert java.lang.String to int

The Property and Value appear directly in the message, so fixing the corresponding key in application.properties resolves it. Classic patterns are typos in key names, or a field marked @NotNull having no value.

Startup failures from profile configuration mistakes

It’s also easy to miss cases where the configuration values themselves are written, but they aren’t loaded because the profile doesn’t match.

Specify the active profile in one of these ways:

# Environment variable
export SPRING_PROFILES_ACTIVE=dev

# JVM argument
java -jar app.jar -Dspring.profiles.active=dev
# application.properties
spring.profiles.active=dev

The naming convention is application-{profile}.properties—if you specify dev, application-dev.properties is loaded. The common application.properties is read first, and profile-specific files override it.

A common pitfall: writing the production spring.datasource.url in application-prod.properties but forgetting to set SPRING_PROFILES_ACTIVE, causing the URL to be null at startup and producing Failed to configure a DataSource. Get in the habit of always checking The following profiles are active: at the top of the startup logs.

When you still can’t figure it out

As a last resort, build a minimal reproducible setup and narrow things down. Specifically, these three:

  • Increase logs with logging.level.org.springframework=DEBUG
  • Temporarily remove unrelated Beans and configurations
  • Explicitly pin the Spring Boot / Java / DB driver versions

Once you’ve reproduced it in this form, it becomes easier to spot the cause yourself, and you’ll also get better answers when asking in an internal chat or on Stack Overflow.

Summary

For startup failure errors, first read the Description and Action. If that’s not enough, use --debug to get more detail. Then recall the typical patterns for each cause category—this flow resolves most cases. Without panicking, walking through from the formatted message in order is, in the end, the shortest path.