Java Build Journey

What This Maven and Tomcat Lab Actually Taught Me

This lab started as a simple Maven exercise, but it ended up teaching something much more useful: how source code becomes a deployable artifact, how that artifact reaches an application server, and why that full path matters in real DevOps work.

Source Code → Maven Lifecycle → WAR Artifact → Tomcat Container → Running Web App

I did not want this lab to become another copy-and-paste command session. I wanted to understand what each tool was doing, why the output mattered, and how I could reason through the workflow without guessing.

That shift in mindset changed the lab completely. It stopped being about memorizing mvn package and started becoming about reading a project, confirming prerequisites, building cleanly, producing an artifact, and deploying that artifact in a repeatable environment.

Main lesson: the important part was not the command itself. The important part was understanding the path from code to a running service and being able to troubleshoot each stage of that path.

Why This Lab Matters Beyond Maven

In a real team, nobody wants a build engineer who only knows the happy-path command. Teams need someone who can identify the build tool, read the project structure, verify versions, find the output, and explain why deployment succeeds or fails.

Project recognition

Seeing pom.xml immediately tells me Maven is the build system and the packaging rules live there.

Artifact awareness

A build is not finished just because logs say success. I need to know exactly what artifact was produced and where it lives.

Deployment thinking

A server or container can be healthy while the application is still missing, broken, or deployed under a different path.

Reading the Project First

The clearest signal that this was a Maven project was the presence of pom.xml. That file defines the project metadata, dependencies, plugins, and packaging type. Once I saw that file, the next questions became obvious:

  • Which Java version does this project expect?
  • Which Maven version is installed locally?
  • What kind of deployable output will Maven produce?
  • Where will that output go after packaging?
Good DevOps work often begins with recognition. Before touching a build, identify the runtime, the build tool, the config file, and the expected artifact.
Step 1

Confirm the Java Runtime

The first practical check was the Java version. That sounds basic, but version mismatches are one of the most common causes of build failures. The screenshot shows Java 17.0.18 installed locally, which aligned with the environment I was using for the lab.

java -version
Terminal output showing Java 17.0.18 installed
Java was the first dependency to confirm because Maven cannot build a Java application without the correct runtime and JDK.
Step 2

Check the Maven Toolchain

After Java, I checked Maven itself. The environment shows Apache Maven 3.9.12, along with the Java runtime Maven is using under the hood. This matters because build behavior can change depending on both the Maven version and the Java version Maven is attached to.

mvn -version
Terminal output showing Apache Maven 3.9.12 and Java 17
This was the point where the environment stopped being a guess. I could see the actual toolchain that would run the build.
Step 3

Start Small with the Maven Lifecycle

Instead of jumping straight to a full build, I walked through the lifecycle in smaller steps. Running mvn validate first gave me a clean check that the project structure and configuration could be read correctly.

mvn validate
Terminal showing mvn validate completed successfully
Validation is a small step, but it tells me the project is at least structurally sound before I spend time on a longer build.
Step 4

Run the Test Phase and Observe the Pipeline

Next came mvn test. In this lab, the test phase completed successfully and reported zero executed tests, but the important point was still clear: Maven has a defined lifecycle, and the test stage is part of that pipeline whether the project has many tests or very few.

mvn test
Terminal showing mvn test succeeded with zero test failures
Even with a simple result, the test phase showed how Maven moves through a predictable workflow instead of acting like a single all-purpose command.
Step 5

Package the Application into a WAR Artifact

Packaging was the step that made the lab feel real. Maven assembled the web application, created the target directory contents, and produced the final artifact: target/vprofile-v2.war.

mvn package
Terminal showing mvn package built vprofile-v2.war in target
This screenshot is where the idea of an artifact became concrete. The build did not just print success; it created a deployable WAR file.
Important distinction: target/ contains the output of this project build, while ~/.m2/repository is Maven's local dependency cache. They are related to the build, but they serve different roles.

This was also the point where mvn clean package made more sense to me. A clean build removes old output first, which reduces the risk of stale compiled files affecting the current result.

Step 6

Prepare the Deployment Environment with Docker

Rather than installing Tomcat directly on my machine, I used Docker to keep the runtime isolated and reproducible. Before doing that, I checked the Docker version so I knew the container runtime was ready.

docker --version
Terminal showing Docker version 27.4.0
Docker became the deployment layer for the lab, which made the Tomcat environment easier to start, reset, and explain.
Step 7

Launch a Tomcat Container

With Docker available, I started a Tomcat container on port 8080. The first run pulled the image from Docker Hub and then created the container locally. That is a useful reminder that the first execution often includes environment setup work beyond just starting a process.

docker run -d -p 8080:8080 --name tomcat tomcat
Terminal showing docker run pulling and starting the Tomcat image
This is where packaging and deployment finally met. I now had a server ready to receive the WAR artifact.
Step 8

Verify the Container Is Actually Running

A deployment flow should never rely on assumption. I checked the running containers to confirm Tomcat was up, mapped to port 8080, and ready for the next step.

docker ps
Terminal output showing the Tomcat container running on port 8080
A healthy container is not the same as a deployed application, but it is the required foundation for the next deployment step.
Step 9

Copy the WAR into Tomcat

Once the container was ready, I copied the built WAR file into Tomcat's webapps directory. This is the handoff moment where the build artifact leaves the build workspace and enters the application server.

docker cp target/vprofile-v2.war tomcat:/usr/local/tomcat/webapps/
Terminal output showing the WAR file copied into the Tomcat container
This was the clearest connection between build output and deployment target: the WAR file moved directly into the server that would host it.
Step 10

Restart Tomcat to Load the Application

After copying the artifact, I restarted the container so Tomcat could detect the WAR cleanly and expand it. This made the deployment state explicit instead of hoping the application would appear automatically.

docker restart tomcat
Terminal output confirming the Tomcat container restart
Restarting the container made the lab easier to reason about: build artifact copied, server restarted, application loaded.
Step 11

Confirm the Application Path and See the Result

The final screenshot shows the application running at http://localhost:8080/vprofile-v2/. That path is important. The app was not deployed at the root URL; it was deployed under the WAR context path. That is a small detail, but it explains a lot of beginner confusion when a container is up and the browser still returns the wrong page or a 404 at /.

Browser showing the deployed vProfile login page on localhost port 8080
This was the payoff: the build artifact became a running web application inside Tomcat, accessible through the correct context path.

What the Lab Actually Taught Me

How code becomes runnable

Source code is only the beginning. It has to be validated, tested, packaged, and deployed before users can interact with it.

What a build tool really does

Maven is not just a command launcher. It is a structured workflow for turning project configuration and source code into a predictable output.

Why artifacts matter

The WAR file is the contract between build and deployment. If I understand the artifact, I understand what must be shipped.

Why isolation helps

Using Docker for Tomcat made the server environment easier to reproduce and kept the deployment story cleaner on my local machine.

Why debugging matters

Every stage can fail for a different reason: wrong versions, dependency problems, missing runtime tools, incorrect paths, or deployment assumptions.

Why this is DevOps foundation

This manual flow is the same logic CI/CD pipelines automate. The pipeline only makes sense if the underlying steps make sense first.

My Biggest Takeaway

The most valuable lesson was not to memorize isolated commands. It was to understand the pattern behind them.

In Java projects, that pattern might be pom.xml, Maven, a WAR or JAR artifact, and an application server. In Node.js, it might be package.json, npm, a build directory, and a runtime service. The tools change, but the thinking stays similar: identify the prerequisites, build the code, produce the output, and deploy it in the right environment.

Once I understood the pattern, the lab stopped feeling like "just a Maven exercise" and started feeling like real infrastructure and delivery thinking.

What I Want to Explore Next

  • Run the full application with all required backend services
  • Automate the build and deployment with Jenkins or GitHub Actions
  • Containerize more of the stack, not just Tomcat
  • Deploy the application as an end-to-end project on AWS

This lab gave me the missing bridge between source code and real deployment. That bridge is exactly where DevOps becomes practical.