JBoss EAP 08: package Spring Boot as a WAR and catch a context-root bug
The same ticket endpoint can pass at the root path yet return a broken Location under /training. Converting the earlier Boot lab to a WAR exposed exactly that assumption: a new test caught the missing deployment prefix. Packaging work can reveal behavior that the original tests never exercised.
The download contains a complete buildable source project, retaining the service, JDBC, H2, and error contracts. Local verification covered WAR packaging, nine tests, and real HTTP under two contexts. Those requests used embedded Tomcat; external EAP deployment and target compatibility remain unverified.
Download lesson 08: WAR project, verifier, and EAP worksheetA WAR can have two startup paths
Executable WAR (verified locally)
java -jar → WarLauncher → main → embedded Tomcat → Spring MVC
External container (not verified here)
EAP starts → loads WAR → servlet initialization
→ SpringBootServletInitializer → application contextWAR describes packaging rather than one mandatory launch mechanism. This lab keeps Boot’s repackaging plugin, so it can also run with java -jar. An external container initializes the web application through the Servlet lifecycle. Identify the path before interpreting startup success.
Change packaging and dependency responsibility
<packaging>war</packaging>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>These are two excerpts from the full POM, not a complete build file. Packaging selects WAR output, while provided assigns the container dependency its role. Java 21, Boot 3.5.16, and the other starters remain unchanged, keeping packaging work separate from framework upgrades.
Inspecting the built archive showed Tomcat core/websocket 10.1.55 in WEB-INF/lib-provided, but tomcat-embed-el in WEB-INF/lib. The validation starter POM also depends on EL at compile scope. Consequently, this artifact does not lose every Tomcat-named JAR. EAP provider ownership for EL still needs a separate check.
Tell the external initializer which application to load
public class LabApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(LabApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(LabApplication.class, args);
}
}The full class retains @SpringBootApplication and the existing configuration registration; imports and annotations are omitted here. configure supplies the application source for external initialization, while main retains executable startup. Compiling both does not verify both lifecycles: external initialization has not been run here.
Reproduce the missing Location prefix
// Previous expression in the root-context JAR lab:
URI.create("/api/tickets/" + ticket.id())
// WAR lab: preserve the actual request context.
URI.create(httpRequest.getContextPath() + "/api/tickets/" + ticket.id())The controller maps /api/tickets; the context root sits before that mapping. getContextPath() is empty for a root deployment and /training for that context. The fix keeps an origin-relative Location while restoring the missing prefix. The original eight tests still cover the previous root-path behavior.
The ninth MockMvc test creates a ticket under /training, checks Location, and follows it to retrieve the same id. Before the fix it failed: expected /training/api/tickets/<id>, actual /api/tickets/<id>. It passed afterward. That negative control shows the test detects this defect, beyond merely reporting a green suite.
The fix covers direct connections and the default servlet mapping. A proxy adding /gateway or a custom servlet path needs additional handling and verification. getContextPath() is not a universal formula for every public URL.
Inspect the artifact, then make real requests
mvn package
python3 verify_war.pyRun inside the extracted spring-boot-war directory with Java 21, Maven 3.6.3 or newer, and Python 3. Initial Maven use downloads dependencies. The script checks class major 65, manifest, application class, and library locations, then starts and stops its own executable WAR on random loopback ports for root and /training contexts.
Seven HTTP checks cover ping, creation, following Location, blank input, malformed JSON, excessive title length, and a missing ticket. The /training run also checks that bare /api/ping returns 404. All 15 passed locally, with output under target/verification. Real HTTP complements the Maven integration tests that observe H2 rollback.
What remains before EAP acceptance
The worksheet records EAP update, JDK, WAR hash, runtime-name, actual context root, and ownership of logging, EL, data sources, and transactions. The lab still manages H2 itself rather than using JNDI, and includes no blanket module-exclusion recipe. Determine target ownership before changing dependencies.
server.address, server.port, and server.servlet.context-path control the executable-mode example; they do not configure EAP listeners or establish loopback-only exposure. The lab has no production authentication, DB2, or MQ integration. Any external deployment belongs in an identified isolated training environment.
Check yourself: which success does not prove the next?
Could java -jar still work without configure? It could, because that path runs main. This is a lifecycle-based inference, not an executed removal experiment. The missing Location prefix, by contrast, was an observed test failure. Separating those categories tells the reviewer which conclusions have direct evidence.
Connect to lesson 07: compare observed evidenceNext: Spring XML and transaction comparison