How to Calculate Application Readiness Using Database, Redis, Solace, Certificates and Startup State
In a Kubernetes or OpenShift environment, a microservice being running does not necessarily mean it is ready to receive traffic.
A Spring Boot application may have successfully started its embedded Tomcat server while:
- The database connection is still being initialized.
- Redis is not available.
- Solace messaging is disconnected.
- Required certificates have not been loaded.
- Application configuration has not finished loading.
- Required caches have not been populated.
- Downstream dependencies are temporarily unavailable.
If Kubernetes starts sending traffic too early, users may receive errors even though the pod itself appears to be running.
This is exactly the problem that Kubernetes Readiness Probes are designed to solve.
1. Liveness vs Readiness
The first concept to understand is that liveness and readiness have different purposes.
| Probe | Question | Failure Action |
|---|---|---|
| Liveness | Is the application alive? | Kubernetes may restart the container |
| Readiness | Can the application handle traffic? | Kubernetes removes the pod from Service endpoints |
| Startup | Has the application finished starting? | Prevents premature liveness/readiness checks |
A useful mental model is:
Kubernetes Pod
|
+----------+----------+
| |
Liveness Readiness
| |
"Am I alive?" "Can I serve traffic?"
| |
Restart Receive traffic
if broken if healthy
2. Why /actuator/info Should Not Be Used
A common configuration is:
livenessProbe:
httpGet:
path: /actuator/info
port: 8080
readinessProbe:
httpGet:
path: /actuator/info
port: 8080
This technically works because Kubernetes only cares about the HTTP response.
However, it is not a good health-check design.
/actuator/info is intended to expose application information such as:
{
"app": {
"name": "order-service",
"version": "1.2.5"
}
}
It does not answer:
“Can this application safely receive production traffic?”
The application could return HTTP 200 from /actuator/info while:
Oracle DOWN
Redis DOWN
Solace DISCONNECTED
Certificates NOT LOADED
Kubernetes would still consider the pod ready.
3. Spring Boot’s Built-in Kubernetes Health Endpoints
Spring Boot provides dedicated health groups for Kubernetes.
The recommended endpoints are:
/actuator/health/liveness
and:
/actuator/health/readiness
Enable Kubernetes probe support:
management:
endpoint:
health:
probes:
enabled: true
health:
livenessstate:
enabled: true
readinessstate:
enabled: true
Expose the health endpoint:
management:
endpoints:
web:
exposure:
include:
- health
- info
- metrics
The resulting endpoints are:
/actuator/health
/actuator/health/liveness
/actuator/health/readiness
4. The Important Difference Between Health and Readiness
Consider:
/actuator/health
This can represent the overall health of the application and may include health indicators such as:
Database
Redis
Disk Space
Messaging
Other dependencies
But Kubernetes readiness should be deliberately designed.
For example:
Readiness
|
+-- Database
+-- Redis
+-- Solace
+-- Certificates
+-- Initial Configuration
+-- Cache Warm-up
Only the dependencies that are genuinely required for the application to serve traffic should determine readiness.
5. Example Enterprise Application
Consider a Spring Boot microservice with:
Spring Boot Service
|
+----------------+----------------+
| | |
Oracle Redis Solace
|
Certificates
At application startup:
Pod Started
|
v
Spring Boot Started
|
v
HTTP Server Available
|
v
Load Certificates
|
v
Connect Oracle
|
v
Connect Redis
|
v
Connect Solace
|
v
Warm Required Cache
|
v
Application Ready
The critical question is:
At which point should Kubernetes start sending traffic?
The answer should be:
After all mandatory initialization has completed successfully.
6. Recommended Readiness State Model
Instead of treating readiness as simply:
UP / DOWN
think about the application as moving through states.
STARTING
|
v
CERTIFICATES_LOADING
|
v
DATABASE_INITIALIZING
|
v
REDIS_INITIALIZING
|
v
SOLACE_INITIALIZING
|
v
CACHE_WARMING
|
v
READY
If a mandatory initialization step fails:
INITIALIZATION
|
v
FAILURE
|
v
NOT READY
This gives you a much more meaningful health model.
7. Using Spring Boot’s Readiness State
Spring Boot provides ApplicationAvailability and readiness state support.
The application can transition to:
ReadinessState.ACCEPTING_TRAFFIC
or:
ReadinessState.REFUSING_TRAFFIC
For example:
@Component
public class ApplicationStartupManager {
private final ApplicationContext applicationContext;
public ApplicationStartupManager(
ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void markReady() {
AvailabilityChangeEvent.publish(
applicationContext,
ReadinessState.ACCEPTING_TRAFFIC);
}
public void markNotReady() {
AvailabilityChangeEvent.publish(
applicationContext,
ReadinessState.REFUSING_TRAFFIC);
}
}
Now your application can explicitly control its readiness state.
8. Coordinating Multiple Dependencies
For an enterprise microservice, we might maintain:
@Component
public class ApplicationReadinessState {
private final AtomicBoolean databaseReady =
new AtomicBoolean(false);
private final AtomicBoolean redisReady =
new AtomicBoolean(false);
private final AtomicBoolean solaceReady =
new AtomicBoolean(false);
private final AtomicBoolean certificatesReady =
new AtomicBoolean(false);
public boolean isReady() {
return databaseReady.get()
&& redisReady.get()
&& solaceReady.get()
&& certificatesReady.get();
}
}
Each initialization component updates its own state.
For example:
databaseReady.set(true);
when database initialization succeeds.
Redis:
redisReady.set(true);
Solace:
solaceReady.set(true);
Certificates:
certificatesReady.set(true);
The overall application becomes ready only when:
Database = READY
Redis = READY
Solace = READY
Certificates = READY
|
v
Application
READY
9. Custom HealthIndicator
Another approach is to expose this application state through a custom Spring Boot HealthIndicator.
@Component
public class ApplicationReadinessHealthIndicator
implements HealthIndicator {
private final ApplicationReadinessState state;
public ApplicationReadinessHealthIndicator(
ApplicationReadinessState state) {
this.state = state;
}
@Override
public Health health() {
if (state.isReady()) {
return Health.up()
.withDetail("application", "READY")
.build();
}
return Health.outOfService()
.withDetail("application", "NOT_READY")
.build();
}
}
The resulting health information could look like:
{
"status": "UP",
"components": {
"applicationReadiness": {
"status": "UP",
"details": {
"application": "READY"
}
}
}
}
10. Make the Readiness Information More Detailed
For production troubleshooting, it is useful to expose the individual states.
@Override
public Health health() {
Health.Builder builder =
state.isReady()
? Health.up()
: Health.outOfService();
return builder
.withDetail("database",
state.isDatabaseReady())
.withDetail("redis",
state.isRedisReady())
.withDetail("solace",
state.isSolaceReady())
.withDetail("certificates",
state.isCertificatesReady())
.build();
}
Example:
{
"status": "OUT_OF_SERVICE",
"components": {
"applicationReadiness": {
"status": "OUT_OF_SERVICE",
"details": {
"database": true,
"redis": true,
"solace": false,
"certificates": true
}
}
}
}
This immediately tells an engineer:
The application is not ready because Solace is unavailable.
11. Do Not Perform Expensive Checks on Every Probe Request
This is an important production consideration.
Avoid implementing readiness like:
GET /actuator/health/readiness
|
+--> SELECT 1 from Oracle
|
+--> Redis PING
|
+--> Solace connection test
|
+--> Certificate validation
every time Kubernetes calls the endpoint.
If Kubernetes checks every few seconds and you have many pods, this creates unnecessary dependency traffic.
Instead:
Dependency Monitoring
|
v
Application State
|
v
Readiness Endpoint
The readiness endpoint should ideally be very fast.
12. Event-Driven Dependency State
For some dependencies, you don’t even need periodic polling.
For example, Solace clients can expose connection/disconnection events.
You can update:
solaceReady.set(true);
when connected.
When disconnected:
solaceReady.set(false);
Similarly, Redis and database connectivity can be monitored using appropriate connection-pool or application-level health mechanisms.
This gives you:
Solace Connected
|
v
solaceReady = true
Solace Disconnected
|
v
solaceReady = false
13. What Happens When Redis Goes Down?
This is where architecture matters.
Suppose:
Application
|
+---- Oracle
|
+---- Redis
Redis goes down.
Should readiness become DOWN?
Scenario A – Redis is mandatory
If the application cannot process requests without Redis:
Redis DOWN
|
v
Readiness DOWN
|
v
OpenShift removes pod from Service
This is appropriate.
Scenario B – Redis is only a cache
If the application can fall back to Oracle:
Redis DOWN
|
v
Read from Oracle
|
v
Application continues
In this case, keeping readiness UP may be the better design.
This is an important architectural decision.
14. What Happens When Solace Goes Down?
Consider a service that receives messages from Solace.
If it cannot consume messages:
Solace DOWN
|
v
Consumer unavailable
Depending on the application’s purpose, you might mark readiness as DOWN.
But consider a service that only publishes messages and can buffer/retry them.
Solace being temporarily unavailable may not require removing the pod from HTTP traffic.
Therefore:
Readiness should represent business capability, not simply dependency availability.
This is one of the most important principles in designing production health checks.
15. Certificates and Readiness
Suppose certificates are loaded after the HTTP server starts.
Tomcat Started
|
v
Certificate Loading
|
v
Certificate Validation
If the application requires those certificates to call downstream systems, it should remain:
NOT READY
until certificate loading succeeds.
For example:
certificatesReady.set(true);
only after:
- Certificate exists.
- Certificate is readable.
- Certificate is valid.
- Required private key is available.
- Required trust chain is available.
16. OpenShift Configuration
Your Deployment configuration could look like:
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
The exact values should be tuned based on actual startup and recovery characteristics rather than copied blindly.
17. Consider a Startup Probe
If your application takes a long time to initialize, a startup probe can be useful.
For example:
startupProbe:
httpGet:
path: /actuator/health
port: 8080
periodSeconds: 10
failureThreshold: 30
This gives the application up to approximately:
30 × 10 seconds = 300 seconds
to start.
During startup probing, Kubernetes doesn’t prematurely apply liveness/readiness behavior in the same way it would once startup has succeeded.
18. Recommended Three-Probe Model
For a production Spring Boot application:
Kubernetes
|
+-----------+-----------+
| | |
Startup Liveness Readiness
| | |
"Started?" "Alive?" "Ready?"
| | |
v v v
Startup JVM/App Business
process health capability
Example:
startupProbe:
httpGet:
path: /actuator/health
port: 8080
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
19. Recommended Spring Boot Configuration
A reasonable baseline is:
management:
endpoints:
web:
exposure:
include:
- health
- info
- metrics
endpoint:
health:
probes:
enabled: true
health:
livenessstate:
enabled: true
readinessstate:
enabled: true
Then configure OpenShift to use:
/actuator/health/liveness
and:
/actuator/health/readiness
20. Recommended Architecture for Your Microservice
For an application with:
- Oracle
- Redis
- Solace
- Certificates
- Initial configuration
- Cache initialization
I would use this architecture:
Spring Boot
|
ApplicationStateManager
|
+-----------------+------------------+
| | | |
v v v v
Database Redis Solace Certificates
| | | |
+----------+----------+--------------+
|
v
Readiness State
|
+----------+----------+
| |
ACCEPTING_TRAFFIC REFUSING_TRAFFIC
| |
v v
OpenShift sends OpenShift removes
traffic pod from Service
The key principle is:
Kubernetes should determine whether the pod receives traffic based on the application’s ability to perform its required business function—not merely whether the JVM process is alive.
21. Readiness State Matrix
A useful way to document your production behavior is:
| Database | Redis | Solace | Certificates | Readiness |
|---|---|---|---|---|
| UP | UP | UP | UP | READY |
| DOWN | UP | UP | UP | NOT READY |
| UP | DOWN | UP | UP | Depends on Redis being mandatory |
| UP | UP | DOWN | UP | Depends on Solace being mandatory |
| UP | UP | UP | DOWN | NOT READY |
| UP | UP | UP | Loading | NOT READY |
| UP | UP | UP | UP + Cache warming | NOT READY |
| UP | UP | UP | UP + Initialization complete | READY |
This matrix should be agreed upon with the application/business team rather than being decided purely from an infrastructure perspective.
22. Important Design Rule: Liveness Should Be Simpler Than Readiness
A common mistake is:
Liveness
|
+-- Oracle
+-- Redis
+-- Solace
+-- External APIs
+-- Certificates
This is dangerous.
Imagine Oracle goes down.
If liveness fails:
Oracle DOWN
|
v
Liveness DOWN
|
v
OpenShift restarts pod
|
v
Pod starts
|
v
Oracle still DOWN
|
v
Restart again
You can end up with a restart loop.
Instead:
Liveness
|
+-- JVM/Application process
Readiness
|
+-- Database
+-- Redis
+-- Solace
+-- Certificates
+-- Required initialization
This allows the pod to remain alive while OpenShift simply stops routing traffic to it.
23. Production Troubleshooting Becomes Much Easier
With a properly designed readiness mechanism, OpenShift can show:
Pod
|
+-- Running
|
+-- Ready: False
You can then inspect:
/actuator/health/readiness
and discover:
Database UP
Redis UP
Solace DOWN
Certificates UP
Instead of seeing:
Pod Restarting
with no obvious reason.
That distinction can significantly reduce troubleshooting time during production incidents.
24. Final Recommended Pattern
For enterprise Spring Boot microservices running on Kubernetes/OpenShift:
Liveness
Use:
/actuator/health/liveness
Keep it lightweight.
Readiness
Use:
/actuator/health/readiness
and make it represent the actual business readiness of the application.
Startup
Use:
startupProbe
when startup is long or unpredictable.
Dependency State
Track critical dependencies:
Oracle
Redis
Solace
Certificates
Required configuration
Cache initialization
Avoid
Do not use:
/actuator/info
as a readiness or liveness endpoint.
Do not perform expensive dependency calls on every Kubernetes probe.
Do not make every dependency automatically mandatory for readiness.
Do not make liveness dependent on external infrastructure.
Conclusion
A Kubernetes health check should not simply answer:
“Is the Spring Boot process running?”
A production-ready microservice should answer three different questions:
Startup
↓
"Have I finished initialization?"
Liveness
↓
"Am I alive and capable of continuing?"
Readiness
↓
"Can I safely receive and process production traffic?"
For an enterprise Spring Boot application using Oracle, Redis, Solace and certificates, the most robust design is to maintain an application-level readiness state, update it as critical initialization and dependency states change, and expose that state through Spring Boot’s Kubernetes readiness endpoint.
This gives Kubernetes/OpenShift a reliable signal and gives engineers a clear explanation of why a pod is not ready, rather than simply reporting that something failed.