Spring Boot
Data JPA
H2 Database
data.sql
Table Not Found

Spring Boot Data JPA with H2 and data.sql - Table not Found

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

This error usually means Spring Boot tried to run data.sql before the table existed. With H2 and Spring Data JPA, that often happens because Hibernate is expected to create the schema, but SQL initialization runs earlier in the startup sequence unless you configure it explicitly.

Why the Table Is Missing

There are two common ways tables get created at startup:

  • Hibernate creates them from your JPA entities
  • 'schema.sql creates them with explicit DDL'

data.sql only inserts data. If Spring executes data.sql before either of those schema creation steps has produced the table, H2 throws a "table not found" error.

This is why the exact initialization order matters more than the database engine itself.

The Most Common JPA-Based Fix

If you want Hibernate to create the tables from your entities and then load data.sql, enable deferred data-source initialization:

properties
1spring.datasource.url=jdbc:h2:mem:testdb
2spring.datasource.driverClassName=org.h2.Driver
3spring.jpa.hibernate.ddl-auto=create
4spring.jpa.defer-datasource-initialization=true

That tells Spring Boot to let JPA schema creation happen before the SQL data script runs.

A minimal entity might look like this:

java
1import jakarta.persistence.Entity;
2import jakarta.persistence.GeneratedValue;
3import jakarta.persistence.Id;
4
5@Entity
6public class Book {
7    @Id
8    @GeneratedValue
9    private Long id;
10
11    private String title;
12
13    protected Book() {
14    }
15
16    public Book(String title) {
17        this.title = title;
18    }
19}

Then data.sql can safely reference the generated table:

sql
insert into book (id, title) values (1, 'Clean Architecture');
insert into book (id, title) values (2, 'Domain-Driven Design');

The Script-Driven Alternative

If you prefer explicit SQL over JPA-generated schema, use schema.sql and data.sql together:

sql
1create table book (
2    id bigint primary key,
3    title varchar(255) not null
4);

With that approach, schema.sql creates the table and data.sql fills it. This avoids depending on Hibernate DDL generation, which can be useful for predictable tests.

When spring.sql.init.mode Matters

For an embedded H2 database, Spring Boot usually initializes SQL scripts automatically. For non-embedded databases, you may need:

properties
spring.sql.init.mode=always

That property does not solve the table-order problem by itself, but it does control whether SQL initialization runs at all.

Prefer One Initialization Strategy

The cleanest setup uses one main schema strategy:

  • JPA/Hibernate for quick test environments
  • 'schema.sql plus data.sql for explicit SQL-managed test setup'
  • Flyway or Liquibase for real migration-driven applications

Mixing several initialization systems can work, but it becomes harder to reason about startup order and harder to debug when tables appear twice or not at all.

A Small Repository Test Example

java
1import static org.assertj.core.api.Assertions.assertThat;
2
3import org.junit.jupiter.api.Test;
4import org.springframework.beans.factory.annotation.Autowired;
5import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
6
7@DataJpaTest
8class BookRepositoryTest {
9
10    @Autowired
11    private BookRepository repository;
12
13    @Test
14    void loadsSeedData() {
15        assertThat(repository.count()).isGreaterThan(0);
16    }
17}

This kind of test quickly reveals whether your startup initialization order is correct.

Common Pitfalls

The biggest mistake is expecting data.sql to create tables. It only inserts rows; it does not define the schema.

Another common issue is relying on Hibernate DDL generation without setting spring.jpa.defer-datasource-initialization=true. In that case, data.sql can run too early.

A third mistake is mixing Flyway or Liquibase with ad hoc schema.sql and data.sql scripts without a clear reason. That makes startup behavior harder to predict.

Summary

  • "Table not found" usually means data.sql ran before the schema existed.
  • If Hibernate creates the schema, use spring.jpa.defer-datasource-initialization=true.
  • If SQL scripts create the schema, put DDL in schema.sql and inserts in data.sql.
  • 'spring.sql.init.mode=always controls whether SQL initialization runs, especially for non-embedded databases.'
  • Prefer one clear database initialization strategy instead of mixing several by accident.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.