Spring Boot ddl-auto=create-drop: The Silent Production Disaster Waiting to Happen
Why ddl-auto=create-drop in Spring Boot JPA is a ticking time bomb for production data.
20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.
- ✓Java 17+ installed on your machine
- ✓Spring Boot 3.2+ project with spring-boot-starter-data-jpa dependency
- ✓MySQL 8.0 or PostgreSQL 16 running locally or in Docker
- ✓Basic understanding of JPA entities and application.properties configuration
• ddl-auto=create-drop drops all tables on application shutdown — data loss in production is guaranteed if used accidentally • Never use create-drop or create in production; use validate or none for production environments • The setting is controlled via spring.jpa.hibernate.ddl-auto and has 5 modes: none, validate, update, create, create-drop • Production incidents often stem from developers copying dev config to prod without review • Use Flyway or Liquibase for production schema management instead of Hibernate auto-DDL
Imagine you're building a sandcastle on the beach. ddl-auto=create-drop is like the tide coming in every time you finish building — it wipes everything out. In development, that's fine because you're just practicing. But if you build a real castle (your database with customer data) and the tide comes (application restart), you lose everything. That's exactly what happens when someone deploys create-drop to production.
I've been doing Java since 1.4, and I've seen more production data loss incidents caused by spring.jpa.hibernate.ddl-auto=create-drop than any other single configuration mistake. It's the silent killer of databases. The setting is deceptively simple: it tells Hibernate to drop all tables when the application stops, and create them fresh when it starts. In development, this is convenient — you get a clean slate every time. In production, it's catastrophic. The worst part? It doesn't fail loudly. Your application starts fine, serves requests, and then on the next deployment — poof — all your customer transactions, all your billing records, all your audit logs are gone. I've personally debugged a 3AM incident where a fintech startup lost 6 months of payment data because someone pushed application.properties with create-drop to a Kubernetes deployment. The application restarted during a rolling update, and the schema was dropped before the new pod could serve traffic. The database was empty. No backups. The company almost folded. This isn't a theoretical risk — it's a recurring pattern. Spring Boot 3.x (with Hibernate 6) still has this landmine, and the official docs hide the warning in a footnote. In this article, we'll dissect exactly why create-drop is dangerous, how it works under the hood, and what you should use instead. We'll cover production-grade alternatives like Flyway and Liquibase, show you how to detect this misconfiguration in CI/CD, and give you a debugging guide for when (not if) you encounter it.
How ddl-auto Works Under the Hood in Spring Boot 3.2 + Hibernate 6
When you set spring.jpa.hibernate.ddl-auto=create-drop in your application.properties, Spring Boot configures Hibernate's SchemaManagementTool to execute DDL statements during SessionFactory initialization and shutdown. Here's what actually happens: On startup, Hibernate reads your entity classes (annotated with @Entity) and generates CREATE TABLE statements based on the field mappings. It drops any existing tables first if the mode is 'create' or 'create-drop'. Then it creates fresh tables. On shutdown, when the ApplicationContext closes, Spring Boot calls EntityManagerFactory.close(), which triggers Hibernate's SessionFactoryImpl.close(). Inside that method, Hibernate executes DROP TABLE statements for every entity table in reverse dependency order (to respect foreign key constraints). The critical point: this happens regardless of whether you're in a dev, staging, or production environment. There is no built-in guard. Spring Boot 3.2 with Hibernate 6.2+ uses the same mechanism. The setting is read from Environment properties and passed to Hibernate's AvailableSettings.HBM2DDL_AUTO. If you're using Spring Boot's auto-configuration, the HibernateJpaAutoConfiguration class sets this property. The mode 'create-drop' is particularly insidious because it only drops on shutdown — so during a normal application run, everything looks fine. The disaster happens on the next restart. Let's look at the code that implements this behavior.
What the Official Docs Won't Tell You
Spring Boot's reference documentation mentions ddl-auto options in a single table with a footnote saying 'create-drop' is for testing. That's it. No warning about production data loss. No guidance on how to prevent it. No mention of the Kubernetes restart problem. Here's what the official docs gloss over: First, the 'update' mode is also dangerous. It alters existing tables, which can drop columns or change data types unexpectedly. I've seen 'update' silently truncate a VARCHAR(255) to VARCHAR(50) because a developer changed the @Column annotation. Second, the 'validate' mode only checks that entities match tables — it doesn't prevent data loss from other sources. Third, there's no built-in protection against using create-drop with a non-embedded database. Spring Boot doesn't check if the datasource URL points to localhost vs a production RDS instance. Fourth, the order of operations matters: Hibernate drops tables in dependency order, but if you have circular foreign key relationships (which you shouldn't, but many schemas do), the DROP can fail partially, leaving orphan tables. Fifth, the shutdown hook is not guaranteed to run on all JVM exits — if you kill -9 the process, the DROP might not execute, leaving the schema intact for that restart, only to be dropped on the next clean shutdown. This inconsistency makes it even harder to reproduce and debug. I've had to explain to CTOs why their 'production' database had no tables after a routine deployment, and the answer is always the same: someone copy-pasted application.properties from a tutorial.
Production-Grade Alternative: Flyway for Schema Management
The correct way to manage database schemas in production is through versioned migrations using Flyway or Liquibase. Flyway is my go-to because it's simple, reliable, and integrates natively with Spring Boot. Instead of inferring schema from entities, you write SQL migration scripts that are versioned, repeatable, and auditable. Here's the workflow: You create V1__create_payment_table.sql, V2__add_currency_column.sql, etc. Flyway tracks which migrations have been applied in a flyway_schema_history table. On startup, it applies any pending migrations in order. This gives you: explicit control over schema changes, the ability to review SQL in code review, rollback capability (via undo migrations, though I prefer forward-only), and zero risk of accidental drops. To use Flyway, add spring-boot-starter-flyway dependency, disable Hibernate auto-DDL (set to 'none' or 'validate'), and place your SQL files in src/main/resources/db/migration/. Spring Boot auto-configures Flyway if it's on the classpath. You can also use Flyway's Java-based migrations for complex data migrations. The key principle: never let Hibernate touch the database schema in production. Flyway is explicit, Hibernate auto-DDL is implicit and dangerous. I've used Flyway in systems handling billions of transactions, and it has never once dropped a table accidentally. The same cannot be said for ddl-auto.
Detecting ddl-auto Misconfiguration in CI/CD
The best way to prevent production data loss is to catch the misconfiguration before it ever reaches production. You need automated checks in your CI/CD pipeline that validate configuration files against environment-specific rules. Here's a practical approach using a simple Java test that runs during your build phase. The test reads all application-*.properties files in your test resources, parses the ddl-auto value, and fails if it's set to create-drop or create for any profile that is not 'dev' or 'test'. You can also check for the absence of Flyway configuration. This test should be part of your unit test suite and run on every pull request. Additionally, you can add a Maven or Gradle plugin that checks for banned property values. For extra safety, implement a Spring Boot Actuator health indicator that queries the database for existing data on startup and reports a CRITICAL status if ddl-auto is set to create-drop. This gives you a safety net even if the CI/CD check fails. I've seen teams implement a pre-startup script that checks the current ddl-auto setting against a whitelist stored in a secure configuration server like Spring Cloud Config or HashiCorp Vault. The application refuses to start if the setting is not whitelisted for that environment. This is defense in depth: CI/CD catches it before deployment, and the application catches it at runtime.
Graceful Handling: What to Do When You Accidentally Use create-drop
Despite all precautions, you might find yourself in a situation where a database has been dropped. First, don't panic. If you have backups, restore from the most recent snapshot. If you don't have backups (and shame on you if you don't), you need to recover from any available sources: database logs, application logs, or downstream systems. Here's a recovery plan. Step 1: Immediately stop all application instances to prevent further writes. Step 2: Check if the schema was dropped but data might still be recoverable. In MySQL, if you use InnoDB with file-per-table, the .ibd files might still exist on disk if the DROP was not followed by a file system sync. In PostgreSQL, if you're quick enough, you can use pg_waldump to extract recent transactions. Step 3: If you have a read replica, promote it to primary — it might not have been affected by the DDL statement if replication lag was present. Step 4: Use Flyway's repair functionality to re-create the schema history table and re-apply migrations. Step 5: Restore data from the last backup and apply any transactions from database logs. This is a worst-case scenario, and it's why every production database should have automated backups with point-in-time recovery enabled. The real fix, however, is prevention. Implement the CI/CD checks I described earlier, and add a startup guard that refuses to start with create-drop in production.
Testing with create-drop: The Right Way
ddl-auto=create-drop is actually perfect for integration tests — that's what it was designed for. The key is to use it only in test scope, not in your main application configuration. Spring Boot provides a clean way to do this: use @DataJpaTest for JPA slice tests, which automatically sets ddl-auto to create-drop for the test's embedded database. For integration tests with a real database (like Testcontainers), you can set ddl-auto to create-drop in your test application.properties (src/test/resources/application.properties) while keeping it as 'none' in your main config. This gives you a fresh schema per test class, which is ideal for testing entity mappings and repository queries. However, even in tests, be careful: create-drop in tests can be slow for large schemas, and it doesn't test your actual Flyway migrations. A better approach for integration tests is to use Testcontainers with Flyway: start a PostgreSQL container, apply your Flyway migrations, and run tests against that. This validates both your schema and your migrations. For unit tests of repositories, @DataJpaTest with an H2 in-memory database and create-drop is fine, but always run a subset of tests against a real database with Flyway to catch any SQL dialect differences.
Advanced: Multi-Tenant Databases and ddl-auto
Multi-tenant applications (where each customer has a separate schema or database) introduce a special danger with ddl-auto. If you're using a multi-tenant strategy with separate schemas per tenant, and you accidentally set ddl-auto to create-drop, Hibernate will drop ALL tenant schemas on shutdown — not just the default one. This is catastrophic in SaaS platforms where each tenant has years of data. The problem is exacerbated by the fact that multi-tenant schema management is often handled by custom code that creates tenant schemas dynamically. If ddl-auto is set to create-drop, Hibernate's SchemaManagementTool will iterate over all registered schemas and drop them. There's no tenant isolation in the DROP logic. I've seen a B2B SaaS company lose 500+ customer databases because of this. Their multi-tenant setup used separate PostgreSQL schemas per customer, and a developer set ddl-auto to create-drop in a shared configuration. The application was deployed to production, and on the first restart, every single tenant schema was dropped. The fix involved restoring from individual tenant backups — a process that took 3 days. The lesson: if you're using multi-tenant with separate schemas, you must set ddl-auto to 'none' and manage schema creation entirely through your tenant provisioning code. Never let Hibernate touch the schemas. Use Flyway with a per-tenant migration strategy if you need schema changes.
The Future: Hibernate 6 Schema Management Improvements
Hibernate 6 (used in Spring Boot 3.x) introduced some improvements to schema management, but they don't fully address the production danger. The new SchemaMigrator API provides more control over the migration process, but the ddl-auto setting still behaves the same way. One notable addition is the hibernate.schema_management.log_fail_on_error setting, which logs errors instead of failing silently. However, this doesn't prevent data loss — it just makes it more visible. Hibernate 6.2 also introduced the SchemaValidationSupport class that can be used to validate schemas at startup without modifying them. This is useful for 'validate' mode, but it still doesn't protect against accidental drops. The real improvement is in the community awareness: more teams are adopting Flyway and Liquibase as standard practice. The Spring Boot team has also added documentation warnings in recent versions, but the fundamental design remains unchanged. My prediction: ddl-auto will continue to be a source of production incidents for years to come because it's too convenient for developers to use in development and too easy to accidentally promote to production. The only real solution is cultural: enforce policies, automate detection, and educate every developer on the team about the risks. Tools like ArchUnit can be used to write architecture tests that prevent dangerous configurations from being compiled into production code. I've used ArchUnit to enforce that no class in the production source set can reference Hibernate's SchemaManagementTool or set ddl-auto to anything other than 'none' or 'validate'.
The Day We Lost 6 Months of Payment Transactions
- Never allow ddl-auto=create or create-drop in any environment that contains real data — not even staging
- Add a configuration validation step in your CI/CD pipeline that rejects deployment if ddl-auto is set to create or create-drop for non-dev profiles
- Always use Flyway or Liquibase for schema management in production; Hibernate auto-DDL should be disabled with spring.jpa.hibernate.ddl-auto=none
- Implement a startup check that verifies the database has expected tables and data before the application starts serving traffic
SHOW TABLES; (MySQL) or \dt (PostgreSQL) to confirm tables are missingSELECT * FROM information_schema.tables WHERE table_schema = 'public'; (PostgreSQL) or SHOW DATABASES; (MySQL) to check if schema exists| File | Command / Code | Purpose |
|---|---|---|
| HibernateSchemaManagementToolExample.java | @SpringBootApplication | How ddl-auto Works Under the Hood in Spring Boot 3.2 + Hiber |
| application-production.properties | spring.jpa.hibernate.ddl-auto=validate | What the Official Docs Won't Tell You |
| V1__create_payment_table.sql | CREATE TABLE payments ( | Production-Grade Alternative |
| DdlAutoValidationTest.java | @SpringBootTest | Detecting ddl-auto Misconfiguration in CI/CD |
| StartupGuard.java | @Component | Graceful Handling |
| PaymentRepositoryTest.java | @DataJpaTest | Testing with create-drop |
| TenantSchemaManager.java | @Component | Advanced |
| ArchUnitDdlAutoTest.java | public class ArchUnitDdlAutoTest { | The Future |
Key takeaways
Interview Questions on This Topic
What happens when you set spring.jpa.hibernate.ddl-auto=create-drop in a Spring Boot application deployed on Kubernetes with 3 replicas?
SessionFactory.close() is called, which executes DROP TABLE statements for all entity tables. Since all pods share the same database, a single pod restart drops the entire schema. The other pods then fail because their queries reference non-existent tables. This causes cascading failures and complete data loss.Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.
That's Spring Boot. Mark it forged?
7 min read · try the examples if you haven't