#300: ProjectForgeApplication.java
projectforge-application/src/main/java/org/projectforge/start/ProjectForgeApplication.java · Spring Boot entry point · 194 lines · 128 code · 49 comments · 17 blank
The single entry point for the entire ProjectForge server. Run with ./gradlew bootRun or java -jar projectforge-application.jar. Annotated with @SpringBootApplication (enables auto-configuration, component scanning, and auto-configuration) and @ServletComponentScan (discovers @WebFilter/@WebServlet annotations for Wicket and REST servlets). Extends SpringBootServletInitializer for traditional WAR deployment support.
Startup Sequence
1. Argument parsing: Custom arguments are extracted before Spring Boot processes them. --base-dir=/path sets the working directory (logs, config, temp files). --time-zone=Europe/Berlin sets TimeZone.setDefault(). Unknown arguments are passed through to Spring Boot for standard handling (port, profiles, etc.).
2. Base directory resolution (priority order):
① --base-dir CLI argument → ② system property projectforge.base-dir → ③ environment variable PROJECTFORGE_BASE_DIR → ④ ~/.projectforge (default)
3. Directory initialization: Creates logs/, temp/, and fonts/ subdirectories under the base directory. Writes the current process PID to temp/projectforge.pid for monitoring and shutdown scripts.
4. TimeZone setup: Sets TimeZone.setDefault() to the configured value (default: UTC). This is critical — all server-side date calculations use this timezone. The frontend may display dates in user-local timezone, but the server operates in a single consistent zone.
5. Version banner: Reads META-INF/projectforge-version.properties (generated by Gradle) and logs the version, build timestamp, and git commit hash.
6. Spring Boot launch: Calls SpringApplication.run() with the fully-configured application context.
Error Handling at Startup
Port conflict: Catches ConnectorStartFailedException — if port 8080 is already in use, reads the existing PID from temp/projectforge.pid and logs a clear message: "Port 8080 is already in use by process PID=XXXXX". Does NOT retry or auto-switch ports — the admin must resolve the conflict manually.
Graceful shutdown: Registers a shutdown hook via SecurityShutdown that clears sensitive data (passwords, tokens, encryption keys) from memory on JVM termination. This is a defense-in-depth measure — if the JVM heap is dumped after shutdown, sensitive data should be zeroed.
Class Hierarchy
ProjectForgeApplication extends SpringBootServletInitializer — this enables deployment as a traditional WAR file in a servlet container (Tomcat, Jetty) in addition to the embedded server mode. The configure() method is overridden to set the sources to ProjectForgeApplication.class, telling the servlet container which Spring configuration class to bootstrap.
The @ServletComponentScan annotation is essential for Wicket compatibility — Wicket registers its filter via @WebFilter annotation rather than Spring Boot's FilterRegistrationBean, and without @ServletComponentScan the Wicket filter would never be discovered.
Git History
| Commit | What changed |
868d6abb7 | Copyright 2025→2026 |
63081666f | Copyright 2024→2025 |
b6092df09 | Copyright 2023→2024 |
ab45d51fa | Copyright 2001-2022→2001-2023 |
5f7ef41b8 | Copyright 2021→2022 |
ceb63e8a1 | Copyright 2001-2021 |
7c79f192 | Copyright 2020 |
dd5ca38ac | Initial commit — ProjectForgeApplication created as the Spring Boot entry point with argument parsing, directory setup, and timezone configuration. |
9ebb88522 | Project creation — this file was part of the initial project structure. |
Why two startup modes? The class supports both java -jar (embedded Tomcat via Spring Boot) and WAR deployment (external Tomcat via SpringBootServletInitializer). The WAR mode is used by organizations that have existing Tomcat infrastructure and prefer deploying ProjectForge alongside other web applications. The JAR mode is simpler for development and Docker deployments.