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

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 // neverNote: 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 folder
src/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.javajava
@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=trueCommandLineRunner will treat this as an array.args[0] = "--env=dev"
args[1] = "debug"
args[2] = "--seed=true"ApplicationRunner will parse this argument asOption 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=alwaysjava
// 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.
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.
