SpringApplication Bootstrap
1. Overview
Every EMS Spring Boot service — admin-service, registration-portal, future admin-portal — follows an identical bootstrap convention: main-class entry point that sets a few pre-context System properties, validates the active profile combination, starts the Spring context, and logs a multi-line startup banner describing what came up. The convention is inherited from JHipster and kept by choice because it has survived contact with production and gives operators a single consistent mental model across services.
This page documents what the banner contains, what profile validation rejects, and the small set of pre-context actions that every service main method performs.
See also: Portal Pattern, Hazelcast Configuration.
2. Main Class Responsibilities
Each service has a single main class under its root package:
| Service | Main class |
|---|---|
admin-service |
|
registration-portal |
|
admin-portal (future) |
|
The main class does three things, in order:
-
Set any pre-context System properties (see below).
-
Invoke
SpringApplication.run(…). -
Call
logApplicationStartup(env)with the resultingConfigurableEnvironment.
Additionally:
-
@PostConstruct initApplication()validates the active profile combination after the context is ready.
2.1. Pre-context System properties
admin-service sets one before the context starts:
public static void main(String[] args) {
System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication app = new SpringApplication(AdminServiceApp.class);
app.setDefaultProperties(defaultProfileProperties());
ConfigurableApplicationContext ctx = app.run(args);
Environment env = ctx.getEnvironment();
logApplicationStartup(env);
}
Reason: DevTools' restart classloader is incompatible with Hazelcast — see Hazelcast Configuration § DevTools Compatibility.
registration-portal does not set this; its Hazelcast footprint is lighter. admin-portal should set it if it mirrors admin-service’s session replication + security dimension caches.
Other pre-context properties can appear here; rule of thumb: anything that needs to be true before @Bean methods execute.
3. Profile Validation
@PostConstruct on the main class:
@PostConstruct
public void initApplication() {
Collection<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)
&& activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) {
log.error("You have misconfigured your application! It should not run with both the 'dev' and 'prod' profiles at the same time.");
}
if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)
&& activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_CLOUD)) {
log.error("You have misconfigured your application! It should not run with both the 'dev' and 'cloud' profiles at the same time.");
}
}
Currently logs an error but does not fail-fast. A stronger variant would throw IllegalStateException and refuse to start — recommended if operators have ever shipped with a bad profile mix. Low-cost change.
4. Startup Banner
After the context is up, logApplicationStartup(env) logs a multi-line block:
----------------------------------------------------------
Application 'event-admin-service' is running! Access URLs:
Local: http://localhost:12504
External: http://10.0.0.42:12504
Profile(s): [prod, kubernetes, otlp]
----------------------------------------------------------
Config Server: -
Database: jdbc:mysql://idealogic-prod.mysql.svc.cluster.local:6446/wpca_prod
----------------------------------------------------------
Values pulled from the environment:
| Field | Source |
|---|---|
Application name |
|
Protocol |
|
Server port |
|
Context path |
|
Local hostname |
|
Profile(s) |
|
Config Server |
|
Database |
|
The banner is the operator’s first diagnostic: wrong port, wrong profile, wrong database URL — all visible in the first screenful of logs.
4.1. No ApplicationListener<ApplicationReadyEvent>
We considered moving the startup logic into an ApplicationReadyEvent listener. Pro: runs after embedded Tomcat is actually accepting requests. Con: the banner also sometimes precedes Hazelcast cluster-join logs, which is useful context. Current convention keeps the log in main() with a comment explaining the position.
If we move to Kubernetes readiness probes (/readyz) as the definitive "ready to serve" signal — we already do — the event listener variant becomes attractive. Deferred; not a blocker.
5. Default Profile Properties
SpringApplication.setDefaultProperties(defaultProfileProperties()) ensures that spring.profiles.default=dev — running without any profile argument uses dev rather than blowing up. This is a JHipster convention we keep.
Effect: java -jar event-admin-service.jar with no flags starts with dev profile. For prod, either --spring.profiles.active=prod,kubernetes on the command line or set SPRING_PROFILES_ACTIVE in the environment.
6. Profile Inventory
| Profile | What it toggles |
|---|---|
|
Default. Liquibase auto-apply with faker data, local H2/MySQL, Hazelcast standalone, DEBUG logging for |
|
Liquibase with |
|
Spring Cloud Kubernetes config + discovery clients; Hazelcast K8s service-DNS discovery; plain-text logback (no ANSI) |
|
Enables |
|
OpenTelemetry spring-boot-starter + javaagent enabled, Logback OTLP appender (see OpenTelemetry Configuration) |
|
Configure |
|
Skip Liquibase on startup; used when the schema is managed out-of-band |
|
Local OIDC testing — enables a dev IdP client registration pointing at localhost Keycloak |
Typical production profile set: prod,kubernetes,api-docs,otlp. Typical dev: just dev. The api-docs profile in prod is optional but keeps Swagger UI reachable for admin debugging; can be dropped if exposure is a concern.
7. Pre-context bootstrap.yml
admin-service has both bootstrap.yml and bootstrap-prod.yml. The bootstrap. family loads *before application.*, and is the right place for Spring Cloud Config client settings or any other property that other property sources depend on.
Typical contents:
spring:
application:
name: event-admin-service
cloud:
kubernetes:
enabled: true
config:
enabled: true
discovery:
enabled: true
Portals can use a bootstrap.yml too; registration-portal’s is minimal. admin-portal should follow the same pattern.
8. Logback Configuration
Shared convention via logback-spring.xml:
-
Pattern includes a
%crlfcustom converter (CRLFLogConverter) — strips CRLF characters from log messages to prevent log-injection exploits -
ANSI colours enabled under
devprofile; plain-text underkubernetes(so collectors like Loki don’t swallow the escape sequences) -
logback-appenderfor OTel trace correlation wired whenotlpprofile active
Reference: admin-service/src/main/resources/logback-spring.xml.
9. Graceful Shutdown
Spring Boot 3’s server.shutdown=graceful is enabled in all services. Shutdown behaviour:
-
Container receives
SIGTERM. -
Spring initiates graceful shutdown — new connections rejected, in-flight requests given up to
spring.lifecycle.timeout-per-shutdown-phase(default 30s) to complete. -
Hazelcast member leaves the cluster; partition migration kicks in on remaining peers.
-
JVM exits.
Kubernetes terminationGracePeriodSeconds should be > timeout-per-shutdown-phase + Hazelcast-leave-window. 60s is a safe default; tuned per service if Hazelcast partition counts grow.
10. Reference
| File | Role |
|---|---|
|
Main class; pre-context System property; profile validation; startup banner |
|
Portal variant of the same pattern |
|
Spring Cloud Kubernetes bootstrap |
|
Logging config with CRLF converter, OTel appender |
|
Active profile defaults; common app properties |