Senior 7 min · March 06, 2026

Spring Boot — ddl-auto=create-drop Destroys Data

Customer data vanished on every restart due to default ddl-auto=create-drop.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Spring Boot is a launcher and opinion layer on top of Spring — it removes setup work, it does not replace Spring
  • @SpringBootApplication combines @Configuration + @EnableAutoConfiguration + @ComponentScan in one annotation
  • Auto-configuration is conditional — @ConditionalOn* guards only activate when classpath and bean conditions are met
  • Starter dependencies solve version mismatches — one starter pulls in a curated, tested, compatible set of libraries
  • Your explicit bean definitions always win — auto-configuration backs off when you define your own bean of the same type
  • Placing @SpringBootApplication in the wrong package is the most common startup failure — component scanning only finds beans in the same package or sub-packages
Plain-English First

Imagine you want to bake a cake. Traditional Spring is like being handed raw ingredients with no instructions — a bag of flour, a carton of eggs, and a cold oven you have to wire yourself before you can even think about baking. You spend your afternoon reading manuals instead of cooking.

Spring Boot is like getting a pre-heated oven set to exactly the right temperature, a mixing bowl already measured out, and a recipe card on the counter that says 'just add your own flavour.' The boring setup decisions are already made. You walk in and start cooking.

The key word is 'convention.' Spring Boot looks at what ingredients you brought — your dependencies — and makes sensible guesses about what you are trying to build. Bring a database driver, it sets up a connection pool. Bring a web dependency, it starts an HTTP server. Bring nothing special, it stays out of your way. When its guess is wrong, you override it with one line in a properties file. That is the whole model: convention over configuration.

Every Java developer hits the same wall. You have a clear idea for a web service, but before writing a single line of business logic you are buried in XML configuration, manually wiring beans, registering a servlet container, and fighting dependency version mismatches between libraries that were never designed to be used together. By the time the environment actually runs, you have forgotten what you were building in the first place.

Spring Boot was created specifically to eliminate that wall. It is why almost every new Java backend project in the industry starts with it today, and why it has become the default answer to 'how do we build a new microservice?' regardless of team size or company.

The core problem Spring Boot solves is bootstrapping friction. The original Spring Framework is genuinely powerful — the dependency injection model, Spring MVC, Spring Data — but famously verbose to configure. A simple REST endpoint in raw Spring MVC could require a web.xml descriptor, a DispatcherServlet configuration, a Spring context configuration class, and a dozen explicit bean definitions before you could write your first business method. Spring Boot replaces all of that with intelligent defaults and conditional auto-configuration: it looks at what is on your classpath and wires things up for you automatically. You bring the feature; Spring Boot brings the scaffolding.

I have set up both from scratch. Configuring raw Spring MVC for a hello-world REST endpoint in 2015 took me the better part of a morning. The equivalent Spring Boot application took under four minutes. That gap has only grown as the ecosystem matured.

By the end of this guide you will understand exactly what Spring Boot is and what it is not, why auto-configuration is the engine that makes everything else work, how to build and run a real layered REST API, and where the specific traps are that catch even experienced developers off guard — including the one that wiped a production database.

What Spring Boot Actually Is — and What It Isn't

Spring Boot is not a replacement for the Spring Framework. This trips up a lot of developers, especially those coming from other ecosystems who assume 'Spring Boot' and 'Spring' are the same thing. They are not. Spring Boot is a launcher and opinion layer built on top of the Spring Framework. It uses the same Spring core underneath — the same dependency injection container, Spring MVC for HTTP handling, Spring Data for database access — but it makes opinionated decisions about how those pieces fit together so you do not have to negotiate that assembly process yourself.

Three pillars hold up everything Spring Boot does.

The first is Auto-Configuration. When your application starts, Spring Boot scans what is on your classpath and automatically creates beans you would otherwise define manually. If it sees spring-boot-starter-web on the classpath, it auto-configures an embedded Tomcat server, registers a DispatcherServlet, configures Jackson for JSON serialization, and sets up error handling. No XML. No explicit bean declarations for any of this. The configuration infers itself from what you brought to the project.

The second is Starter Dependencies. Instead of hunting for compatible Maven or Gradle versions of a dozen different Spring libraries — a process that reliably produces version conflicts — you add one starter and it pulls in everything that works together, versioned correctly. spring-boot-starter-data-jpa gives you Hibernate, Spring Data JPA, a connection pool, and JDBC in one dependency declaration with tested version compatibility.

The third is the Embedded Server. Your application ships as a single executable JAR with Tomcat (or Jetty or Undertow) embedded inside it. No deploying WAR files to an external application server. No managing server versions. No checking whether the target Tomcat version supports your servlet API version. You run java -jar myapp.jar and the server starts.

The most important mental model: Spring Boot does not add features to Spring. It removes the work of configuring them. When you write a controller, a service, a repository — that is Spring. When those components get wired together and an HTTP server starts without you touching a configuration file, that is Spring Boot.

io/thecodeforge/bookstore/BookstoreApplication.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package io.thecodeforge.bookstore;

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

/**
 * io.thecodeforge: The entry point for the Bookstore API.
 *
 * @SpringBootApplication is a composed annotation — three annotations in one:
 *
 *   @SpringBootConfiguration   (specialization of @Configuration)
 *   -> Marks this class as a source of @Bean definitions
 *   -> This class can declare beans directly if needed
 *
 *   @EnableAutoConfiguration
 *   -> Triggers auto-configuration by reading AutoConfiguration.imports
 *      from every JAR on the classpath
 *   -> Each listed config class is evaluated against @ConditionalOn guards
 *   -> Only classes where all conditions pass are activated
 *
 *   @ComponentScan
 *   -> Scans this package (io.thecodeforge.bookstore) and ALL sub-packages
 *   -> Finds @Component, @Service, @Repository, @Controller, @RestController
 *   -> Registers discovered classes as beans in the ApplicationContext
 *
 * Package placement rule: this class MUST be in the root package.
 * Controllers in io.thecodeforge.bookstore.controller -> found.
 * Controllers in com.other.controller -> NOT found. 404 on every endpoint.
 */
@SpringBootApplication
public class BookstoreApplication {

    public static void main(String[] args) {
        // SpringApplication.run:
        // 1. Creates the ApplicationContext (the Spring IoC container)
        // 2. Registers all auto-configured beans
        // 3. Scans for @Component classes and registers them
        // 4. Starts the embedded Tomcat server on port 8080
        SpringApplication.run(BookstoreApplication.class, args);
    }
}
Output
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
:: Spring Boot :: (v3.3.0)
INFO --- [main] i.t.b.BookstoreApplication : Starting BookstoreApplication using Java 21
INFO --- [main] i.t.b.BookstoreApplication : No active profile set, falling back to default profiles: default
INFO --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
INFO --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path ''
INFO --- [main] i.t.b.BookstoreApplication : Started BookstoreApplication in 2.847 seconds (process running for 3.1)
Spring Boot Is a Scaffold, Not a Replacement
  • Auto-configuration scans your classpath and creates beans you would otherwise define manually — it fills gaps and backs off when you provide your own definitions
  • Starters are curated, tested dependency groups — one starter replaces manually hunting for 12 compatible library versions and eliminates version conflict errors
  • The embedded server means your application is a self-contained artifact — one JAR, one command, same behavior on every machine that has Java installed
  • @SpringBootApplication is shorthand for three annotations — break them apart with @ComponentScan(basePackages=...) when your code spans multiple package trees
  • Spring Boot is opinionated but overridable — define your own bean and the auto-configured default backs off automatically, which is the mechanism, not a side effect
Production Insight
A team placed their @SpringBootApplication class in io.thecodeforge.app while their controllers lived in com.company.controllers — a different package tree entirely, not a sub-package. @ComponentScan found nothing in com.company because it was never instructed to look there. Every HTTP endpoint returned 404 with no error anywhere in the logs — the controllers were not broken, they simply did not exist from Spring's perspective. The team spent six hours debugging routing configuration, filter chains, and security settings before someone compared the main class package to the controller package. The fix was one line: moving the main class to the common root package. The lesson that stuck was checking package structure before anything else when endpoints return 404 without explanation.
Key Takeaway
Spring Boot is a launcher and opinion layer on top of Spring — it removes setup work by making conditional wiring decisions based on your classpath, it does not replace Spring.
The three pillars are auto-configuration, starter dependencies, and the embedded server — each eliminates a distinct category of manual setup work.
Package placement of @SpringBootApplication is the most common source of silent 404 failures — the main class must be at the root package that is the parent of all other packages in your application.
Component Scanning and Package Structure Decisions
IfAll application code lives under one package tree — for example, io.thecodeforge.bookstore.*
UsePlace @SpringBootApplication at the root package (io.thecodeforge.bookstore) — component scan finds all sub-packages automatically
IfCode spans multiple package trees — for example, io.thecodeforge and com.company.shared
UseUse @ComponentScan(basePackages = {"io.thecodeforge", "com.company.shared"}) to explicitly declare all roots — do not rely on default scanning to find packages outside the main class package
IfNeed to exclude specific packages from scanning — test fixtures, generated code, or legacy components
UseUse @ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "io\\.thecodeforge\\.generated\\..*")) to exclude by pattern
IfMulti-module Maven or Gradle project with separate modules for controller, service, and repository layers
UseEnsure the main application class module includes all other modules as dependencies and that sub-module packages are children of the root package declared in the main class
IfLibrary JAR provides beans that should be auto-configured in consuming applications
UseRegister the library's configuration class in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports — component scan in the consuming application will not find it, only auto-configuration discovery will

Auto-Configuration: The Engine Under the Hood

Auto-configuration is the most powerful feature in Spring Boot and the most misunderstood concept when developers first encounter it. Candidates in interviews say 'Spring Boot configures itself automatically' as though that is a complete explanation. It is not. The mechanism behind that statement is what determines whether you can actually debug Spring Boot when it misbehaves — and it will misbehave.

When your application starts, Spring Boot invokes AutoConfigurationImportSelector, which reads a file called META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports from every JAR on your classpath. In Spring Boot 2.x this was META-INF/spring.factories — same idea, different file. The spring-boot-autoconfigure JAR alone lists over 150 configuration classes in this file. Every starter you add to pom.xml can contribute more.

Each listed class is annotated with @ConditionalOn guards that function as evaluation criteria. DataSourceAutoConfiguration has @ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class}) and @ConditionalOnMissingBean(DataSource.class). Both conditions must pass before the class is activated and its @Bean methods are executed. If there is no JDBC driver class on the classpath, the first condition fails and the entire DataSourceAutoConfiguration class is skipped. If you have already defined your own DataSource bean, the second condition fails and it is skipped again. Either way, nothing is created.

This is the model that makes 'opinionated but overridable' work in practice. You do not fight auto-configuration to override it. You simply define your own bean and the auto-configured one backs off. Want a custom ObjectMapper with specific date formatting? Define a @Bean that returns an ObjectMapper and JacksonAutoConfiguration detects it via @ConditionalOnMissingBean and steps aside entirely.

The practical implication for debugging: when something is not working the way you expect, the question is always 'is the relevant auto-configuration firing or not, and why?' The --debug flag answers this definitively. Run java -jar myapp.jar --debug and look for the CONDITIONS EVALUATION REPORT section in the output. Positive matches show what fired. Negative matches show what was skipped and the exact condition that caused the skip. There is no guessing required — the report tells you precisely what happened and why.

io/thecodeforge/bookstore/controller/BookController.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package io.thecodeforge.bookstore.controller;

import io.thecodeforge.bookstore.model.Book;
import io.thecodeforge.bookstore.service.BookService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * io.thecodeforge: REST controller for book operations.
 *
 * Auto-configuration that made this controller work without any manual setup:
 *   - @RestController is found by @ComponentScan (part of @SpringBootApplication)
 *   - Spring MVC DispatcherServlet was auto-configured by WebMvcAutoConfiguration
 *     because spring-boot-starter-web is on the classpath
 *   - Jackson ObjectMapper was auto-configured by JacksonAutoConfiguration
 *     — Book objects are serialized to JSON automatically
 *   - Embedded Tomcat was started by EmbeddedWebServerFactoryCustomizerAutoConfiguration
 *
 * None of these required manual bean definitions. The classpath told Spring Boot
 * what to configure and the @ConditionalOn guards made it happen.
 */
@RestController
@RequestMapping("/api/books")
public class BookController {

    // Constructor injection: the correct pattern in production Spring Boot code.
    // Dependencies are final — immutable after construction.
    // Class is testable without a Spring context — new BookController(mockService).
    // Missing dependencies cause a compile-time error, not a runtime NullPointerException.
    private final BookService bookService;

    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    @GetMapping
    public ResponseEntity<List<Book>> getAllBooks() {
        return ResponseEntity.ok(bookService.findAllBooks());
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Book createBook(@RequestBody Book newBook) {
        return bookService.saveBook(newBook);
    }

    @GetMapping("/{bookId}")
    public ResponseEntity<Book> getBookById(@PathVariable Long bookId) {
        // Optional.map avoids a null check — if the book exists, return 200 with it.
        // If it does not exist, return 404 with an empty body.
        // orElse(ResponseEntity.notFound().build()) handles the absent case cleanly.
        return bookService.findBookById(bookId)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    @DeleteMapping("/{bookId}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteBook(@PathVariable Long bookId) {
        bookService.deleteBook(bookId);
    }
}
Output
// POST /api/books with body {"title": "Clean Code", "author": "Robert Martin", "price": 29.99}
HTTP 201 Created
{
"id": 1,
"title": "Clean Code",
"author": "Robert Martin",
"price": 29.99
}
// GET /api/books/1
HTTP 200 OK
{
"id": 1,
"title": "Clean Code",
"author": "Robert Martin",
"price": 29.99
}
// GET /api/books/9999
HTTP 404 Not Found
(empty body — ResponseEntity.notFound().build())
// DELETE /api/books/1
HTTP 204 No Content
(empty body — @ResponseStatus(HttpStatus.NO_CONTENT))
The Conditions Evaluation Report Is Your Primary Debugging Tool
Run java -jar myapp.jar --debug and look for the CONDITIONS EVALUATION REPORT in the startup output. It is divided into three sections: Positive matches (auto-configurations that fired and created beans), Negative matches (auto-configurations that were skipped and the exact condition that caused the skip), and Unconditional classes (always activated). Every 'Negative matches' entry shows the class name, the @Conditional annotation that failed, and what it evaluated. If DataSourceAutoConfiguration is in Negative matches with 'did not find required class com.zaxxer.hikari.HikariDataSource', you know exactly what dependency is missing. If it says 'existing bean of type javax.sql.DataSource found', you know your own bean is in control. I reach for this report before any other debugging step when a bean is missing or a feature is not activating as expected. It removes all guesswork.
Production Insight
A team added spring-boot-starter-data-jpa to a service that was not yet connected to a database. The starter's transitive dependencies included HikariCP. DataSourceAutoConfiguration saw HikariCP's class on the classpath, found no user-defined DataSource bean, and attempted to create a connection pool using the default localhost:5432 target. The application crashed at startup with a connection refused error to a database that did not exist in their environment. The team spent three hours investigating network configuration and firewall rules. Running --debug would have shown DataSourceAutoConfiguration in Positive matches within seconds of the crash, making the cause immediately obvious. After this incident, the team added 'run once with --debug and verify Positive matches' to their new-service checklist.
Key Takeaway
Auto-configuration is conditional assembly driven by @ConditionalOn guards — it is not magic, it is a traceable decision process that evaluates your classpath, existing beans, and application properties.
Your explicit bean definitions always win — auto-configuration never overrides a bean you have defined, it detects it via @ConditionalOnMissingBean and backs off.
The CONDITIONS EVALUATION REPORT from --debug is the definitive answer to 'why is this auto-configuration not firing' — check it before investigating anything else.
Debugging Auto-Configuration Issues
IfExpected bean is missing from the context at startup — injection fails with NoSuchBeanDefinitionException
UseRun with --debug and find the relevant auto-configuration class in Negative matches — the report shows the exact @Conditional condition that failed and what it evaluated
IfAuto-configuration fires but uses wrong settings — wrong database URL, wrong connection pool size
UseOverride the specific properties in application.properties (spring.datasource.url, spring.datasource.hikari.maximum-pool-size) — redefining the entire bean is rarely necessary
IfAuto-configuration conflicts with your custom configuration — NoUniqueBeanDefinitionException
UseAdd @Primary to your custom bean for unambiguous resolution, or exclude the conflicting auto-configuration via spring.autoconfigure.exclude in application.properties
IfApplication starts slower than expected — many auto-configurations evaluated unnecessarily
UseRemove unused starters from pom.xml — each one pulls in auto-configuration classes that are evaluated at every startup even when all conditions fail
IfCustom auto-configuration class in a library JAR is never discovered
UseRegister the class in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports — component scanning does not discover auto-configuration classes, only the imports file does

Building the Complete Bookstore: Model, Service, Repository, and Configuration

A controller without the layers beneath it is not a real application — it is a sketch. The production pattern in Spring Boot is a clear separation of concerns: the controller handles HTTP concerns, the service contains business logic, the repository handles data access, and the model defines the domain shape. Each layer has a single responsibility and can be tested independently.

This separation matters beyond code organization. When a business rule changes, you modify the service without touching the controller. When you switch from H2 to PostgreSQL, you change application.properties without touching any Java class. When you write a unit test for the service's business logic, you mock the repository and run the test without starting a server or a database.

The Book model below uses @Entity and @Id — Spring Data JPA annotations that map the class to a database table. Spring Data JPA is itself auto-configured when spring-boot-starter-data-jpa is on the classpath and a DataSource bean exists. The pattern repeats across every Spring Boot feature: add a starter, write your domain code, and Spring Boot handles the infrastructure wiring between them.

The application.properties file is where configuration lives outside the code. Port numbers, database URLs, feature flags, connection pool sizes — all externalized here. Spring Boot supports environment-specific property files through profiles: application-dev.properties activates when SPRING_PROFILES_ACTIVE=dev, application-prod.properties activates when SPRING_PROFILES_ACTIVE=prod. The same JAR, the same compiled code, different runtime behavior driven by which profile is active. This is how you have H2 in development and PostgreSQL in production without conditional logic anywhere in your Java code.

io/thecodeforge/bookstore/BookstoreComponents.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// ── Model ────────────────────────────────────────────────────────────────────
package io.thecodeforge.bookstore.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Positive;

/**
 * io.thecodeforge: JPA entity — maps to the 'books' table.
 *
 * @Entity tells Hibernate this class represents a database table.
 * @Table(name = "books") overrides the default table name (would be 'book').
 * @Id marks the primary key field.
 * @GeneratedValue delegates key generation to the database.
 */
@Entity
@Table(name = "books")
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank(message = "Title is required")
    @Column(nullable = false)
    private String title;

    @NotBlank(message = "Author is required")
    private String author;

    @Positive(message = "Price must be a positive number")
    private double price;

    // JPA requires a no-argument constructor — Hibernate uses it when
    // reconstructing objects from database rows
    public Book() {}

    public Book(String title, String author, double price) {
        this.title = title;
        this.author = author;
        this.price = price;
    }

    public Long getId() { return id; }
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    public String getAuthor() { return author; }
    public void setAuthor(String author) { this.author = author; }
    public double getPrice() { return price; }
    public void setPrice(double price) { this.price = price; }
}

// ── Repository ───────────────────────────────────────────────────────────────
package io.thecodeforge.bookstore.repository;

import io.thecodeforge.bookstore.model.Book;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * JpaRepository<Book, Long> provides:
 *   findAll(), findById(), save(), delete(), count() — all auto-implemented.
 * Spring Data JPA generates the implementation at startup — no SQL, no boilerplate.
 * Add custom query methods by naming convention: findByAuthor(String author)
 * or by @Query annotation for complex queries.
 */
public interface BookRepository extends JpaRepository<Book, Long> {
    // Custom finder — Spring Data JPA generates: SELECT * FROM books WHERE author = ?
    java.util.List<Book> findByAuthor(String author);
}

// ── Service ──────────────────────────────────────────────────────────────────
package io.thecodeforge.bookstore.service;

import io.thecodeforge.bookstore.model.Book;
import io.thecodeforge.bookstore.repository.BookRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;

@Service
public class BookService {

    private final BookRepository bookRepository;

    // Constructor injection — final field, explicitly documented dependency
    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    // readOnly = true: signals Hibernate to skip dirty checking — performance optimization
    // for queries that do not modify data
    @Transactional(readOnly = true)
    public List<Book> findAllBooks() {
        return bookRepository.findAll();
    }

    @Transactional(readOnly = true)
    public Optional<Book> findBookById(Long id) {
        return bookRepository.findById(id);
    }

    @Transactional
    public Book saveBook(Book book) {
        return bookRepository.save(book);
    }

    @Transactional
    public void deleteBook(Long id) {
        bookRepository.deleteById(id);
    }
}
Output
// Application startup — Hibernate DDL output with ddl-auto=update:
Hibernate: create table books (
id bigint generated by default as identity,
author varchar(255) not null,
price float(53),
title varchar(255) not null,
primary key (id)
)
// POST /api/books {"title": "Effective Java", "author": "Joshua Bloch", "price": 45.00}
Hibernate: insert into books (author, price, title) values (?, ?, ?)
HTTP 201 Created — {"id": 1, "title": "Effective Java", "author": "Joshua Bloch", "price": 45.00}
// GET /api/books?author=Joshua%20Bloch (using findByAuthor custom finder)
Hibernate: select b.id, b.author, b.price, b.title from books b where b.author=?
HTTP 200 OK — [{"id": 1, "title": "Effective Java", "author": "Joshua Bloch", "price": 45.00}]
ddl-auto Is the Most Dangerous Default in Spring Boot — Set It Explicitly for Every Environment
spring.jpa.hibernate.ddl-auto controls what Hibernate does to your database schema at startup and shutdown. The defaults depend on which database you are using, which is part of why this setting causes so much damage when teams do not explicitly set it. With an embedded database like H2, the default is create-drop — Hibernate creates the schema at startup and drops everything at shutdown. This is intentional for development where the database is throwaway. With an external database like PostgreSQL, the default is none — Hibernate does not touch the schema. The production incident described in this guide happened precisely because the team never explicitly set this value. In development with H2, create-drop worked fine. In production with PostgreSQL, the same setting that was implicitly applied from the H2 default carried over and destroyed the schema on every restart. Production rule: ddl-auto=validate checks that the existing schema matches your entities without modifying anything. Schema changes go through Flyway or Liquibase — versioned SQL migration files checked into the repository. Schema migrations are code changes with history, review, and rollback capability.
Production Insight
The ddl-auto incident described in this guide was not an isolated event — I have seen variations of it at three different companies, always following the same pattern. Local development worked for weeks with H2 and create-drop. The team moved to a shared PostgreSQL instance in staging, explicitly set ddl-auto=create to have Hibernate generate the schema for them (convenient, no SQL required). When they promoted the same configuration to production with real customer data, the next deployment dropped and recreated every table. The data was gone. The pattern that prevents this is treating ddl-auto as a deployment configuration value that must be explicitly set in every environment-specific properties file and verified in the deployment checklist — not something inherited from defaults or carried over from a previous environment.
Key Takeaway
Separation of concerns — controller handles HTTP, service contains business logic, repository handles data access — makes each layer independently testable and maintainable as the application grows.
Never rely on default ddl-auto settings — explicitly set spring.jpa.hibernate.ddl-auto in every environment-specific properties file, use validate or none in production, and manage schema with Flyway or Liquibase.
application.properties externalizes everything that varies between environments — database URL, port, feature flags, connection pool sizes — the same JAR runs differently in each environment without recompilation.
Database Configuration Decisions — Environment by Environment
IfLocal development environment with throwaway test data — no persistent data needed
UseUse H2 in-memory with ddl-auto=create-drop — schema is fresh on every restart, no cleanup needed, fast startup
IfShared development or staging environment with persistent data that should survive restarts
UseUse PostgreSQL or MySQL with ddl-auto=validate and Flyway managing schema — data persists, schema changes are explicit and reviewable
IfProduction environment with customer data
UseUse ddl-auto=validate with Flyway or Liquibase — schema managed in versioned SQL migrations, never modified by Hibernate at runtime
IfNeed to switch database engines across environments without code changes
UseUse Spring profiles with application-dev.properties and application-prod.properties — same compiled code, different runtime configuration controlled by SPRING_PROFILES_ACTIVE
IfNeed to initialize test data on application startup for development or integration testing
UseUse src/main/resources/data.sql with ddl-auto=create — Spring Boot loads data.sql after Hibernate creates the schema, populating tables with test data automatically

The Complete pom.xml: What Goes In and Why

Understanding the pom.xml structure is where the starter model becomes concrete. Most tutorials show you to copy the dependencies section without explaining what each entry does and why it is structured the way it is. That gap creates real problems when something goes wrong — you cannot reason about dependency conflicts or missing features if you do not know what each starter contributes.

The spring-boot-starter-parent entry is doing more work than it appears. It imports the spring-boot-dependencies BOM, which defines the exact version of every library that Spring Boot works with. Every starter you add inherits version management from this BOM — you write the dependency without a version number and the BOM supplies the correct one. This is why you almost never specify versions inside a starter-based project. The BOM guarantees that all 150+ libraries in the ecosystem work together at the versions Spring Boot tested.

The starter pattern consolidates what would otherwise be 8 to 12 individual dependency declarations into one. spring-boot-starter-web alone pulls in spring-web, spring-webmvc, spring-boot-starter, spring-boot-starter-tomcat, spring-boot-starter-json, jackson-databind, and their transitive dependencies — all at compatible versions. Without the starter, you would specify each separately and spend time verifying that your Jackson version works with your Spring MVC version works with your Tomcat version.

pom.xmlXML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!--
      spring-boot-starter-parent provides:
        1. spring-boot-dependencies BOM — version management for 150+ libraries
        2. Default Maven plugin configurations (compiler at Java 17+, test runner)
        3. Resource filtering for application.properties placeholder resolution
      Without this parent, you would need to specify library versions individually
      and verify their compatibility manually.
    -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.0</version>
        <relativePath/>
    </parent>

    <groupId>io.thecodeforge</groupId>
    <artifactId>bookstore-api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>bookstore-api</name>
    <description>Bookstore REST API — io.thecodeforge demonstration project</description>

    <properties>
        <!-- Java 21 LTS — the current long-term support version as of 2026 -->
        <java.version>21</java.version>
    </properties>

    <dependencies>
        <!--
          spring-boot-starter-web pulls in:
            spring-web, spring-webmvc, embedded Tomcat, Jackson JSON serialization.
          Without this: no HTTP server, no @RestController processing, no JSON output.
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--
          spring-boot-starter-data-jpa pulls in:
            Hibernate, Spring Data JPA, JDBC, HikariCP connection pool.
          Without this: no @Entity processing, no JpaRepository, no @Transactional.
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!--
          spring-boot-starter-validation pulls in:
            Hibernate Validator (JSR-303 implementation).
          Without this: @NotBlank, @Email, @Size annotations on DTOs are ignored.
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <!--
          spring-boot-starter-actuator adds:
            /actuator/health, /actuator/metrics, /actuator/info endpoints.
          Used for Kubernetes liveness/readiness probes and monitoring integration.
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!--
          H2 in-memory database — runtime scope means it is available at runtime
          but not included in the final production JAR.
          For development and testing only — replace with PostgreSQL driver in production.
        -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!--
          spring-boot-starter-test pulls in:
            JUnit 5, Mockito, AssertJ, Spring Test, MockMvc, Testcontainers support.
          test scope — never included in the production JAR.
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!--
              spring-boot-maven-plugin creates the executable Fat JAR.
              Without this plugin, mvn package produces a standard JAR that cannot
              run standalone — it lacks the Spring Boot launcher and embedded server.
              repackage goal wraps the standard JAR inside a Fat JAR with BOOT-INF layout.
            -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
Output
// mvn clean package produces:
// target/bookstore-api-0.0.1-SNAPSHOT.jar (Fat JAR — executable, ~45MB)
// target/bookstore-api-0.0.1-SNAPSHOT.jar.original (thin JAR — not executable)
//
// Inspect the Fat JAR layout:
// jar tf target/bookstore-api-0.0.1-SNAPSHOT.jar | head -20
// META-INF/MANIFEST.MF
// META-INF/
// BOOT-INF/classes/io/thecodeforge/bookstore/BookstoreApplication.class
// BOOT-INF/classes/io/thecodeforge/bookstore/model/Book.class
// BOOT-INF/lib/spring-boot-3.3.0.jar
// BOOT-INF/lib/spring-boot-autoconfigure-3.3.0.jar
// BOOT-INF/lib/spring-web-6.1.0.jar
// BOOT-INF/lib/tomcat-embed-core-10.1.0.jar
// ...(all dependency JARs nested inside)
//
// Run the Fat JAR:
// java -jar target/bookstore-api-0.0.1-SNAPSHOT.jar
// java -jar target/bookstore-api-0.0.1-SNAPSHOT.jar --debug (with Conditions Report)
// java -jar target/bookstore-api-0.0.1-SNAPSHOT.jar --server.port=9090 (override port)
Do Not Override Individual Dependency Versions Unless You Have a Specific Reason
The spring-boot-dependencies BOM manages versions for every library in the Spring Boot ecosystem based on testing combinations that are known to work together. When you override a single version — specifying <version>2.15.4</version> on Jackson inside a spring-boot-starter project — you step outside the tested compatibility matrix. The consequences are usually subtle: a method that exists in version 2.15.4 but was removed in 2.16.0, a serialization behavior that changed between minor versions, or a class that is present but has a different signature than what another library expects. These surface as ClassNotFoundException or NoSuchMethodError at runtime, not at compile time, and they are difficult to trace back to the version override. Resist the urge. If you must override for a specific security patch or a required feature, document the exact reason in the pom.xml comment and test thoroughly with the overridden version.
Production Insight
A team overrode the Hibernate version inside spring-boot-starter-data-jpa to get access to a new query feature. The override was undocumented — it was added in a hurry during a feature sprint and forgotten. Six months later, a Spring Boot upgrade changed the expected Hibernate version and the two versions had incompatible SessionFactory APIs. The application failed to start with a cryptic NoSuchMethodError that pointed nowhere obviously useful. The team spent a day investigating before someone ran mvn dependency:tree and noticed the version discrepancy. Adding a comment next to the version override — 'Required for feature X, remove when Spring Boot 3.4+ is adopted' — would have prevented the entire investigation.
Key Takeaway
spring-boot-starter-parent provides BOM-based version management for 150+ libraries — adding a starter without specifying a version is intentional, not an oversight.
Each starter is a curated dependency group that replaces 8 to 12 individual declarations with tested, compatible versions — this is the practical solution to dependency hell.
Do not override individual library versions inside a BOM-managed project without documentation and thorough testing — incompatibilities surface at runtime, not at compile time.
Choosing Starters and Managing Dependencies
IfBuilding a REST API that returns JSON responses
UseAdd spring-boot-starter-web — provides Spring MVC, embedded Tomcat, and Jackson JSON serialization
IfPersisting data to a relational database with JPA entities
UseAdd spring-boot-starter-data-jpa and a database driver (h2 for development, postgresql or mysql-connector-j for production)
IfNeed DTO validation with @NotBlank, @Email, @Size constraints
UseAdd spring-boot-starter-validation — without it, JSR-303 annotations on DTO classes are ignored entirely
IfDeploying to Kubernetes and need health probes or exposing metrics
UseAdd spring-boot-starter-actuator — provides /actuator/health/readiness, /actuator/health/liveness, and /actuator/metrics without any additional configuration
IfNeed to override a specific library version for a security patch
UseUse Maven properties defined in the parent BOM where possible (e.g., <jackson.version>2.17.0</jackson.version>) rather than hardcoding versions in individual dependency declarations — document the reason in a comment
● Production incidentPOST-MORTEMseverity: high

The Database That Vanished on Every Restart — ddl-auto=create-drop in Production

Symptom
Customer data disappeared completely every time the application was redeployed. Support tickets flooded in after each release cycle — users reported empty account dashboards, missing order history, and reset preferences. The database had data before the deployment window began and was completely empty after the application came back online. The operations team initially confirmed the database itself was healthy and accessible.
Assumption
The team spent a full day investigating the CI/CD pipeline, suspecting the deployment process was pointing to the wrong database instance or that a recent backup restore had been triggered erroneously. They audited connection strings, deployment scripts, and database access logs. Every check came back clean — the right database, the right credentials, the right host. The problem was not in the infrastructure.
Root cause
spring.jpa.hibernate.ddl-auto was never explicitly set in application.properties. When Spring Boot detects an embedded database like H2, it defaults ddl-auto to create-drop — Hibernate drops all tables when the application shuts down and recreates them fresh on the next startup. This behavior is intentional for local development where the database is throwaway. When the team deployed to production with PostgreSQL, no one noticed that this default had followed them. Hibernate connected to the real production database and dropped every table on each application shutdown, then recreated the empty schema on startup. The data was not migrated somewhere else — it was deleted.
Fix
spring.jpa.hibernate.ddl-auto=validate was set explicitly in application-prod.properties — this instructs Hibernate to check that the existing database schema matches the entity definitions without making any modifications. Flyway was adopted for versioned database migrations, with all schema changes defined in numbered SQL files checked into the repository alongside application code. A deployment checklist was added that requires explicit verification of ddl-auto values per environment before any release is approved. Integration tests were added that assert ddl-auto is not set to create or create-drop when the active Spring profile is prod.
Key lesson
  • Never rely on default ddl-auto settings — explicitly set spring.jpa.hibernate.ddl-auto for every environment in its own application-{profile}.properties file
  • create-drop is only safe for in-memory development databases where data is intentionally ephemeral — it destroys all table data on every application shutdown without warning
  • Use validate or none in production and manage all schema changes through versioned migrations with Flyway or Liquibase — schema changes become code changes with history, review, and rollback
  • Test against the same database engine in staging as in production — H2 compatibility mode masks configuration behaviors that only surface with PostgreSQL or MySQL under real conditions
  • Add a startup assertion or integration test that fails if ddl-auto is create or create-drop outside of a development or test profile — catches the configuration error before it reaches production
Production debug guideWhen your Spring Boot application fails to start or behaves unexpectedly at startup, here is the diagnostic sequence that gets to root cause fastest.6 entries
Symptom · 01
Every endpoint returns 404 with NoHandlerFoundException and no controller error in the logs
Fix
Check the package structure of your @SpringBootApplication class relative to your controllers. If the main class is in io.thecodeforge.app but controllers are in com.other.controllers, @ComponentScan will not find them — those packages are not sub-packages of the main class package. Move the main class to the common root package, or add @ComponentScan(basePackages = {"io.thecodeforge", "com.other"}) explicitly. Verify the fix by checking the startup log for 'Mapped request' lines that list your endpoints.
Symptom · 02
Expected bean is missing from the ApplicationContext — NoSuchBeanDefinitionException at injection points
Fix
Run the application with --debug flag and read the CONDITIONS EVALUATION REPORT in the startup output. Find the auto-configuration class that should create the missing bean and look for 'did not match' alongside it. Common causes are a missing classpath dependency (@ConditionalOnClass failed), an existing user-defined bean of the same type (@ConditionalOnMissingBean triggered), or a required property not being set (@ConditionalOnProperty failed). The report tells you the exact condition that failed in plain text.
Symptom · 03
Application crashes on startup with PortAlreadyInUseException — address already in use on port 8080
Fix
Find which process is holding the port: lsof -i :8080 on Mac/Linux or netstat -ano | findstr :8080 on Windows. If it is another instance of your application that did not terminate cleanly (common in development), kill it with kill -9 <PID>. If it is a different service, change the port with server.port=8081 in application.properties. In Docker environments, check for port mapping conflicts between containers or between the host and the container.
Symptom · 04
NoSuchBeanDefinitionException at startup — a dependency cannot be auto-wired
Fix
Check three things in sequence: (1) does the missing bean's class require a specific starter that is not in pom.xml, (2) is a @ConditionalOn guard preventing auto-configuration from creating it — check the Conditions Report under Negative matches, (3) is the bean's class in a package that @ComponentScan reaches — the class must be in the same package as @SpringBootApplication or a sub-package of it.
Symptom · 05
Application starts but all JSON responses have missing fields or the wrong format
Fix
Check if you have a custom ObjectMapper @Bean defined anywhere in the application. If you do, JacksonAutoConfiguration backs off and your ObjectMapper is used instead — without the default Spring Boot configuration applied to it. Run with --debug and look for JacksonAutoConfiguration in the Negative matches section. If it shows 'did not match' with reason 'existing bean of type ObjectMapper', your custom ObjectMapper is taking over. Either remove it, or configure it explicitly with the Spring Boot defaults as a starting point.
Symptom · 06
Application starts slowly — over 30 seconds for a simple REST API with no complex initialization
Fix
Run with --debug and count the Negative matches in the Conditions Evaluation Report. Each auto-configuration class on the list is evaluated at startup even if it is rejected. Remove starters for technologies you are not using — spring-boot-starter-data-mongodb, spring-boot-starter-amqp, spring-boot-starter-data-redis — each pulls in auto-configuration classes that must be evaluated even if all conditions fail. As a temporary measure while identifying the real culprits, try spring.main.lazy-initialization=true to defer bean creation until first use.
★ Spring Boot Startup Debugging Cheat SheetQuick-reference commands for diagnosing Spring Boot startup and production issues. Each entry maps a specific observable symptom to the commands that get you to the answer fastest.
Port already in use — application fails to bind to port 8080 on startup
Immediate action
Identify which process holds the port before changing anything — it might be a previous instance of your own application
Commands
lsof -i :8080
kill -9 $(lsof -t -i :8080)
Fix now
Kill the conflicting process using the PID from lsof output, or change server.port in application.properties to an available port. On Windows use: netstat -ano | findstr :8080 then taskkill /PID <pid> /F
Bean not found — NoSuchBeanDefinitionException at startup or NoHandlerFoundException for every endpoint+
Immediate action
Print the auto-configuration conditions report to see exactly why the expected bean was not created
Commands
java -jar myapp.jar --debug 2>&1 | grep -A 3 'CONDITIONS EVALUATION'
java -jar myapp.jar --debug 2>&1 | grep 'did not match'
Fix now
Read the 'did not match' lines — they tell you the exact @Conditional annotation that failed and what it evaluated. Add the missing starter, fix the package structure, or define the bean explicitly based on the reason.
Application runs out of memory — OutOfMemoryError in logs or OOMKill in Kubernetes+
Immediate action
Capture a heap dump before the process terminates — this is the only window into what was in memory
Commands
jcmd $(pgrep -f spring-boot) GC.heap_dump /tmp/heapdump.hprof
kubectl describe pod <pod-name> | grep -A 5 'Last State'
Fix now
Open the heap dump in Eclipse MAT and check the dominator tree for the largest retained objects. Add -XX:+HeapDumpOnOutOfMemoryError to get automatic dumps on the next occurrence. Increase -Xmx as a temporary measure while investigating root cause.
Database connection refused on startup — ECONNREFUSED to localhost:5432 or data source URL error+
Immediate action
Verify the database process is running and the connection details in application.properties match
Commands
docker ps | grep postgres
pg_isready -h localhost -p 5432 -U forge_admin
Fix now
Start the database container if it is not running. Verify spring.datasource.url, spring.datasource.username, and spring.datasource.password in application.properties. For local development, switch to H2 in-memory database to decouple development from a running database server.
Traditional Spring MVC vs. Spring Boot
Feature / AspectTraditional Spring MVCSpring Boot
Setup time for a working REST endpoint30 to 60 minutes — web.xml, dispatcher config, context config, explicit bean declarations all required before the first line of business logicUnder 5 minutes — starter dependency, @SpringBootApplication, @RestController, done
Server deployment modelCompile to WAR, deploy to external Tomcat or JBoss, manage server version separately from application versionCompile to Fat JAR, run with java -jar — server version is part of the application, consistent across every environment
Dependency managementManually specify compatible versions for Spring core, Spring MVC, Jackson, validation API, Hibernate — version conflicts are commonDeclare starters without versions — spring-boot-dependencies BOM manages compatible versions for the entire ecosystem
Configuration styleMostly explicit XML or @Configuration Java classes — every bean, every mapping, every serializer declared manuallyConvention-based with application.properties overrides — declare nothing for the common case, override specific settings when needed
Auto-configurationNone — every bean must be declared explicitly, nothing is inferred from the classpathConditional auto-configuration based on classpath, existing beans, and properties — the Conditions Evaluation Report makes every decision transparent
Production readinessManual setup required for health endpoints, metrics, and monitoring integration — each team implements differentlyspring-boot-starter-actuator adds /actuator/health, /actuator/metrics, and /actuator/loggers instantly — Kubernetes-compatible probes out of the box
Learning curveSteep — many moving parts that must be understood and wired together before anything worksGentle entry point, same Spring depth available — you can be productive quickly and go deep when you need to
Best forMaintaining existing Spring applications already running in production on external servers with established deployment pipelinesAny new Java backend project regardless of size — the defaults are production-grade and everything is overridable when your requirements differ

Key takeaways

1
Spring Boot is a launcher and opinion layer on top of the Spring Framework
it does not replace Spring's core concepts of dependency injection, Spring MVC, and Spring Data. It removes the setup work of configuring them.
2
Auto-configuration is conditional, not magical
@ConditionalOn guards evaluate your classpath, existing beans, and application properties to decide what to configure. The Conditions Evaluation Report from --debug makes every decision visible and traceable.
3
Your explicit bean definitions always win over auto-configuration
when you define a bean of the same type, @ConditionalOnMissingBean detects it and the auto-configured default steps aside entirely.
4
Constructor injection is the correct way to wire dependencies
it makes dependencies explicit, enables final field immutability, and allows unit tests to use the class without starting a Spring context.
5
Package placement of @SpringBootApplication is the most common source of silent 404 failures
the main class must be at the root package that is the ancestor of all controllers, services, and repositories.
6
Never rely on default ddl-auto settings
explicitly set spring.jpa.hibernate.ddl-auto in every environment-specific properties file. Use validate or none in production and manage schema changes with Flyway or Liquibase.

Common mistakes to avoid

5 patterns
×

Placing the main class in a non-parent package relative to controllers and services

Symptom
Every HTTP endpoint returns 404 with NoHandlerFoundException. No error in the logs — the controllers are correctly annotated, the application starts without errors, but Spring never discovered the controllers because @ComponentScan only searched the main class's package and its sub-packages. Sibling packages and unrelated package trees are invisible to the default scan.
Fix
Always put the @SpringBootApplication class at the root package that is the ancestor of all other packages in your application — for example, io.thecodeforge.bookstore as the parent of io.thecodeforge.bookstore.controller, io.thecodeforge.bookstore.service, and io.thecodeforge.bookstore.repository. If your code genuinely spans multiple package trees, add @ComponentScan(basePackages = {"io.thecodeforge", "com.company"}) explicitly to declare all roots.
×

Using @Autowired on fields instead of constructor injection

Symptom
Unit tests fail with NullPointerException on the field because the @Autowired field is null without a running Spring context — Mockito's @InjectMocks does not reliably inject into private fields. The class cannot be instantiated outside a Spring context, which makes pure unit testing impossible without additional test infrastructure. Circular dependency bugs are hidden until runtime because Spring works around them with CGLIB proxies instead of failing at construction.
Fix
Use constructor injection with final fields for all mandatory dependencies. The constructor makes every dependency explicit — the compiler enforces that all required dependencies are provided, mocks can be passed directly to the constructor in tests (new BookService(mockRepository)), and final fields guarantee immutability after construction. If a class has so many constructor parameters that it feels awkward, that is a signal to split the class, not a reason to use field injection.
×

Not explicitly setting spring.jpa.hibernate.ddl-auto per environment

Symptom
Application works perfectly with H2 locally using the create-drop default — schema is rebuilt on every restart, which is convenient. When deployed to PostgreSQL in staging or production, the same behavior applies to a database with real data. Every application restart drops all tables, recreating an empty schema. Data is permanently lost without any warning or error log entry — Hibernate logs the DROP TABLE statements at INFO level if Hibernate SQL logging is enabled, but not otherwise.
Fix
Explicitly set spring.jpa.hibernate.ddl-auto in every environment-specific properties file. Use create or create-drop in application-dev.properties for local development with disposable data. Use validate in application-prod.properties for production — this checks that the existing schema matches entity definitions without modifying anything. Manage actual schema changes through Flyway or Liquibase versioned SQL migration files checked into source control.
×

Not running with --debug to understand what auto-configuration activated

Symptom
Unexpected beans appear in the context — a DataSource is created for a database that does not exist, causing startup failure. Or expected beans are missing — JdbcTemplate is not available even though the datasource is configured. Hours are spent manually tracing which auto-configuration class is responsible when the Conditions Evaluation Report would have shown the exact reason in seconds.
Fix
Run java -jar myapp.jar --debug or set logging.level.org.springframework.boot.autoconfigure=DEBUG at least once during the initial setup of any new service. Read the CONDITIONS EVALUATION REPORT — Positive matches show what auto-configuration activated, Negative matches show what was skipped and the exact condition that caused the skip. Make this a standard step in the new-service checklist.
×

Overriding individual library versions inside a starter dependency without documentation

Symptom
Application compiles successfully but fails at runtime with ClassNotFoundException, NoSuchMethodError, or subtle behavioral changes. The overridden library version is incompatible with another library that the BOM manages — they share an API that changed between versions. The incompatibility is not obvious at compile time and can take significant time to trace back to the version override.
Fix
Resist overriding individual versions unless you have a specific, documented reason such as a security patch for a CVE. Use the BOM property names where they exist — Spring Boot's parent POM exposes version properties like jackson.version and hibernate.version that can be overridden in the project's properties section. Add a comment documenting why the override exists and when it can be removed. Test thoroughly with the integration test suite after any version override.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What is the internal difference between @SpringBootApplication and @Enab...
Q02JUNIOR
Explain the Starters mechanism in Spring Boot. How does Maven manage tra...
Q03SENIOR
If you define a bean of the same type as one provided by Spring Boot Aut...
Q04SENIOR
How do you implement an external configuration pattern in Spring Boot to...
Q05SENIOR
What is the Fat JAR layout? How does Spring Boot's custom classloader ha...
Q01 of 05SENIOR

What is the internal difference between @SpringBootApplication and @EnableAutoConfiguration? Under what specific scenario would you define them separately?

ANSWER
@SpringBootApplication is a composed meta-annotation that combines three annotations: @SpringBootConfiguration (a specialization of @Configuration that marks the class as a primary configuration source), @EnableAutoConfiguration (the auto-configuration trigger), and @ComponentScan (which scans the current package and sub-packages for stereotyped components). @EnableAutoConfiguration is specifically responsible for importing AutoConfigurationImportSelector, which reads META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports from every JAR on the classpath and evaluates each listed configuration class against its @ConditionalOn guards. You would break @SpringBootApplication into its constituent annotations when you need custom component scan behavior that the composed annotation cannot express. The most common case: your application code spans multiple package trees that are not in a parent-child relationship. If @SpringBootApplication is in io.thecodeforge.app and controllers are in com.company.controllers, the default @ComponentScan misses the controllers entirely. Breaking the annotations apart lets you write @ComponentScan(basePackages = {"io.thecodeforge", "com.company"}) explicitly while keeping @EnableAutoConfiguration for auto-configuration. Another case: a test configuration class that should not trigger component scanning at all — you use @EnableAutoConfiguration alone without @ComponentScan to avoid picking up production beans in the test context.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
Do I need to know the Spring Framework before learning Spring Boot?
02
What is the difference between Spring Boot and Spring MVC?
03
Why does Spring Boot embed Tomcat instead of requiring an external server?
04
How do I handle database schema migrations in a production Spring Boot application?
05
How do I debug why a specific auto-configuration class did not activate?
🔥

That's Advanced Java. Mark it forged?

7 min read · try the examples if you haven't

Previous
Java Memory Leaks and Prevention
21 / 28 · Advanced Java
Next
Maven vs Gradle in Java