Loading Initial Data in a Spring Boot 4 Application

Loading Initial Data in a Spring Boot 4 Application

Populating data at the startup time in a Spring Boot application.

22 Aug 2026

In order to set up the test data or seed the data in the database, we can pre-populate or initialize the sample data when the application starts.
There are different ways to do this in Spring Boot. We will discuss each of them.

Startup Data Loading Lifecycle

Article Image

Datasource and Schema Creation

Initially, the database initialization script will be executed during startup. Here, we will create the SQL schema file inside the resources. i.e resources/schema.sql
-- schema.sql
CREATE TABLE IF NOT EXISTS products (
    id VARCHAR(255) PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    price DOUBLE PRECISION NOT NULL
);
Now, we will inject some data by creating an SQL file to inject data into it src/main/resources/data.sql
INSERT INTO products (id, name, price) VALUES ('p-100', 'SQL script Espresso Beans', 15.55);
By default, Spring initializes the data with the script if using an in-memory database. To initialize it for other databases, we can configure the properties. Also, disable the automatic schema on startup by setting spring.jpa.hibernate.ddl-auto to none to use the schema file.
application.properties
spring.jpa.hibernate.ddl-auto=none
spring.sql.init.mode=always // never
Note: If the script causes an exception, then the application will fail to start.
Besides this, we can use schema migration tools like Flyway or Liquibase. We can create the migration script under the resources foldersrc/main/resources/db/migration/V1__init_products.sql
CREATE TABLE products (
    id VARCHAR(255) PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    price DOUBLE PRECISION NOT NULL
);

INSERT INTO products (id, name, price) VALUES ('p-100', 'Flyway Espresso Beans', 15.55);

Using the ApplicationRunner or CommandLineRunner

We can use ApplicationRunner or CommandLineRunner to run specific code, i,e injecting data into the database during startup, just before the application finishes boot-up, i,e before SpringApplication.run() completed.

CommandLineRunner

Let's create a configuration class where we will use these beans to load the data.
DataInitializer.java
java
package com.csbyte.taskpulse.datainit;

import com.csbyte.taskpulse.requestlifecycle.Product;
import com.csbyte.taskpulse.requestlifecycle.ProductRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DataInitializer {

    @Bean
    CommandLineRunner seedViaCommandLineRunner(ProductRepository repository) {
        return args -> {
            System.out.println("Executing CommandLineRunner...");
            repository.save(new Product("p-101", "CommandLineRunner Keyboard", 200.00));
        };
    }
}
Here, this bean will receive the raw string array arguments from the main method.

ApplicationRunner

This is similar to the CommandLineRunner, but it parses the arguments into the object ApplicationArguments . This is useful when we are using flags like --env=dev . To test it, we are using the same class as above DataInitializer
Inside DataInitializer.java
java
    @Bean
    ApplicationRunner seedViaApplicationRunner(ProductRepository repository) {
        return (ApplicationArguments args) -> {
            if (args.containsOption("seed")) {
                System.out.println("Executing ApplicationRunner...");
                repository.save(new Product("p-103", "ApplicationRunner Mouse", 50.00));
            }
        };
    }
Provide the --seed=true as an argument to test.

Difference Between CommandLineRunner and ApplicationRunner

The main difference is how they parse the command-line arguments.
Let's look at the command-line arguments example:
--env=dev debug --seed=true
CommandLineRunner will treat this as an array.
args[0] = "--env=dev"
args[1] = "debug"
args[2] = "--seed=true"
ApplicationRunner will parse this argument as
Option Arguments: env=dev, seed=true. Non-option arguments: ["debug"]
So, we can check like this without parsing:
java
if (args.containsOption("seed")) {}

Using ApplicationReadyEvent(Recommended)

This will execute after the Spring Boot application has fully started and all the other runners have completed. We can use it to load the initial data.

Using Functional Bean Listener

Inside DataInitializer.java
java
    @Bean
    ApplicationListener<ApplicationReadyEvent> seedViaFunctionalBean(ProductRepository repository) {
        return event -> {
            System.out.println("Executing ApplicationReadyEvent Functional Bean...");
            repository.save(new Product("p-103", "ApplicationReady Functional Desk Mat", 25.00));
        };
    }

Using EventListener Annotation

java
package com.csbyte.taskpulse.datainit;

import com.csbyte.taskpulse.requestlifecycle.Product;
import com.csbyte.taskpulse.requestlifecycle.ProductRepository;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.stereotype.Component;
import org.springframework.context.event.EventListener;

@Component
public class AnnotationEventListenerSeeder {
    private final ProductRepository repository;

    public AnnotationEventListenerSeeder(ProductRepository repository) {
        this.repository = repository;
    }

    @EventListener(ApplicationReadyEvent.class)
    public void seedOnReady() {
        System.out.println("Executing @EventListener(ApplicationReadyEvent)...");
        repository.save(new Product("p-104", "EventListener Monitor Stand", 85.00));
    }
}
This is a different way to load the initial data into the database. We can use this mechanism for other logic that requires this lifecycle.
To run all the above code, we are using h2 memory databse where the application properties file looks as below.
# Enable H2 In-Memory Database
spring.datasource.url=jdbc:h2:mem:productdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

# Automatically create schema on startup
spring.jpa.hibernate.ddl-auto=update

# Enable H2 Web Console (Optional: View DB at http://localhost:8080/h2-console)
spring.h2.console.enabled=true

logging.level.org.springframework.jdbc.datasource.init=DEBUG

# Enable SQL script execution
spring.sql.init.mode=always
java
// product.java
package com.csbyte.taskpulse.requestlifecycle;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "products")
public class Product {

    @Id
    private String id;
    private String name;
    private double price;

    public Product() {}

    public Product(String id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public String getId() { return id; }
    public void setId(String id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public double getPrice() { return price; }
    public void setPrice(double price) { this.price = price; }
}
java
// ProductRepository.java

package com.csbyte.taskpulse.requestlifecycle;

import org.springframework.data.repository.CrudRepository;

public interface ProductRepository extends CrudRepository<Product, String> {
}
If you run the application, you can see logs similar to the following:
Executing ApplicationRunner...
Executing CommandLineRunner...
Executing ApplicationReadyEvent Functional Bean...
Executing @EventListener(ApplicationReadyEvent)...
Open the H2 database using the URL: http://localhost:8080/h2-console login it and select the product table; you can see all the data injected into the database.
Article Image

Conclusion

In this tutorial, we learned different ways to populate data while loading a Spring Boot application.
Using an SQL script that is executed during the DataSource creation before the application runs. Using a runner for programmatic data seeding. An application listener for loading data after the application has finished starting.