1.The Problem: Where Do We Initialize Database Data?
The Problem: Where Do We Initialize Database Data?
A common need in Spring Boot applications is seeding or resetting data at startup — for example, setting all task statuses back to PENDING after a server restart, or pre-populating a lookup table.
The question is: which lifecycle hook is the right place to do this?
We have two main candidates: @PostConstruct and ApplicationRunner. Choosing the wrong one can cause runtime errors that are hard to debug — a @Repository or EntityManager that looks wired but isn't fully ready yet will throw a NullPointerException or a JPA initialization error silently during startup.
This article walks through the Spring Boot startup sequence to show exactly why ApplicationRunner is the safe choice for database work, and when @PostConstruct is appropriate instead.
2.Full Sequence
Full Sequence
The embedded server (Tomcat/Netty) will open its ports only after all of the ApplicationRunners have returned.
This is enforced inside SpringApplication.run():
3.ApplicationRunner
ApplicationRunner
3.1.Imports
Imports
ApplicationRunner comes from Spring Boot — no extra dependency needed beyond spring-boot-starter:
3.2.Example
Example
Use @Order(Ordered.HIGHEST_PRECEDENCE) when we have multiple runners and need this one to go first.
3.3.Why Repositories Are Safe Inside ApplicationRunner
Why Repositories Are Safe Inside ApplicationRunner
"Safe" here means the JPA infrastructure is fully initialized — EntityManagerFactory, the DataSource connection pool, and all @Repository proxies are ready to accept calls.
Attempting the same DB work inside @PostConstruct is risky because that hook fires during bean creation, before the full context refresh completes: JPA proxies may exist as objects but their underlying infrastructure may not be wired yet, leading to LazyInitializationException or NullPointerException at runtime.
By the time ApplicationRunner.run() is called, the full ApplicationContext is refreshed:
| Infrastructure | Status |
|---|---|
EntityManagerFactory | Initialized |
DataSource connection pool | Open |
@Repository proxies | Fully wired |
| Transaction manager | Ready |
4.@PostConstruct
@PostConstruct
4.1.Imports
Imports
@PostConstruct is part of the Jakarta EE annotations, available in Spring Boot via spring-boot-starter (bundled through jakarta.annotation-api):
Remark. For older Spring Boot 2.x projects still on Java EE, the import is javax.annotation.PostConstruct instead.
4.2.Example
Example
5.@PostConstruct vs ApplicationRunner
@PostConstruct vs ApplicationRunner
@PostConstruct | ApplicationRunner | |
|---|---|---|
| Timing | During bean creation | After full context startup |
| Repositories safe to use | Risky (JPA infra may not be ready) | Always safe |
| Order control | @DependsOn | @Order(n) |
| Server accepting requests | Not yet | Not yet (both run before) |
| Recommended for DB init | No | Yes |
6.Rule of Thumb
Rule of Thumb
-
Use
@PostConstructfor pure in-memory initialization; -
Use
ApplicationRunnerfor anything touching the database or other fully-wired Spring infrastructure.








