Running Maven Builds: From Testing to EC2 Deployment
Whether you’re shipping a microservice or a full-stack Java application, Maven is the backbone of most JVM build pipelines. This guide walks through the full lifecycle — running tests, packaging your app, and deploying it to an AWS EC2 instance — with practical commands you can drop straight into your workflow.
Prerequisites
Before you start, make sure you have:
- Java 11+ installed (
java -version) - Maven 3.6+ installed (
mvn -version) - An AWS EC2 instance running (Amazon Linux 2 or Ubuntu)
- SSH access to the instance (your
.pemkey file) - Java installed on the EC2 instance
Part 1: Running Tests with Maven
Maven’s test lifecycle is straightforward. The key phases you’ll use are test, verify, and surefire:test.
Run all unit tests
mvn test
This compiles your source code and test code, then runs all unit tests found under src/test/java. Test reports land in target/surefire-reports/.
Run tests and continue on failure
mvn test -Dmaven.test.failure.ignore=true
Useful in CI when you want to collect all test failures rather than stopping at the first one.
Run a single test class
mvn test -Dtest=UserServiceTest
Or target a specific method:
mvn test -Dtest=UserServiceTest#shouldReturnUserById
Run integration tests
Integration tests typically live in the verify phase, often powered by the Maven Failsafe plugin:
mvn verify
This runs both unit tests (*Test.java) and integration tests (*IT.java) in sequence.
Skip tests entirely
mvn package -DskipTests
Note:
-DskipTestsskips test execution but still compiles test classes. Use-Dmaven.test.skip=trueto skip compilation too.
Part 2: Building the Artifact
Once tests pass, package your application into a deployable artifact — typically a .jar or .war.
Build a JAR
mvn clean package
clean wipes the target/ directory first. package compiles, tests, and packages. Your artifact ends up at:
target/your-app-1.0.0.jar
Build a fat/uber JAR (Spring Boot)
If you’re using Spring Boot or the Maven Shade plugin, mvn package produces a self-contained executable JAR with all dependencies bundled:
mvn clean package -DskipTests
Run it directly:
java -jar target/your-app-1.0.0.jar
Build for a specific Maven profile
Maven profiles let you swap configs — for example, dev vs prod database URLs:
mvn clean package -Pprod -DskipTests
Part 3: Deploying to EC2
With your artifact built, the next step is getting it onto the EC2 instance. Here are the most practical deployment options.
Option A: SCP + SSH (Simple and Direct)
The most straightforward approach for smaller teams or manual deployments.
Step 1 — Copy the JAR to EC2:
scp -i ~/.ssh/your-key.pem \
target/your-app-1.0.0.jar \
ec2-user@<YOUR_EC2_IP>:/home/ec2-user/app/
On Ubuntu instances, replace
ec2-userwithubuntu.
Step 2 — SSH into the instance:
ssh -i ~/.ssh/your-key.pem ec2-user@<YOUR_EC2_IP>
Step 3 — Run the application:
cd /home/ec2-user/app
nohup java -jar your-app-1.0.0.jar > app.log 2>&1 &
Option B: Maven Wagon Plugin
Wire the SCP step directly into your Maven build. Add this to your pom.xml:
<build>
<extensions>
<extension>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh</artifactId>
<version>3.5.3</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>wagon-maven-plugin</artifactId>
<version>2.0.2</version>
<configuration>
<serverId>ec2-server</serverId>
<url>scp://ec2-user@<YOUR_EC2_IP>/home/ec2-user/app</url>
<fromFile>target/your-app-1.0.0.jar</fromFile>
<toFile>your-app.jar</toFile>
</configuration>
</plugin>
</plugins>
</build>
Add your EC2 SSH key to ~/.m2/settings.xml:
<servers>
<server>
<id>ec2-server</id>
<privateKey>/path/to/your-key.pem</privateKey>
</server>
</servers>
Then deploy with a single command:
mvn clean package wagon:upload-single -DskipTests
Option C: GitHub Actions CI/CD Pipeline
For a fully automated pipeline, here’s a GitHub Actions workflow that tests, builds, and deploys on every push to main:
# .github/workflows/deploy.yml
name: Build and Deploy to EC2
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Java
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Run tests
run: mvn test
- name: Build artifact
run: mvn clean package -DskipTests
- name: Deploy to EC2
env:
EC2_HOST: $
EC2_USER: $
EC2_KEY: $
run: |
echo "$EC2_KEY" > key.pem
chmod 600 key.pem
scp -i key.pem -o StrictHostKeyChecking=no \
target/*.jar $EC2_USER@$EC2_HOST:/home/$EC2_USER/app/app.jar
ssh -i key.pem -o StrictHostKeyChecking=no $EC2_USER@$EC2_HOST \
"pkill -f 'java -jar' || true && \
nohup java -jar /home/$EC2_USER/app/app.jar > /home/$EC2_USER/app/app.log 2>&1 &"
rm key.pem
Store EC2_HOST, EC2_USER, and EC2_SSH_KEY as GitHub repository secrets.
Part 4: Running as a systemd Service (Recommended for Production)
Instead of nohup, manage the process with systemd — it restarts automatically on crash and survives reboots.
Create a service file on EC2:
sudo nano /etc/systemd/system/myapp.service
[Unit]
Description=My Java Application
After=network.target
[Service]
User=ec2-user
WorkingDirectory=/home/ec2-user/app
ExecStart=/usr/bin/java -jar /home/ec2-user/app/app.jar
SuccessExitStatus=143
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
Check status and tail logs:
sudo systemctl status myapp
sudo journalctl -u myapp -f
To deploy a new version, copy the new JAR and restart:
sudo systemctl restart myapp
Quick Reference
| Goal | Command |
|---|---|
| Run all tests | mvn test |
| Run integration tests | mvn verify |
| Run a single test | mvn test -Dtest=MyTest |
| Build (skip tests) | mvn clean package -DskipTests |
| Build for prod profile | mvn clean package -Pprod |
| Copy JAR to EC2 | scp -i key.pem target/app.jar ec2-user@HOST:/path/ |
| SSH to EC2 | ssh -i key.pem ec2-user@HOST |
| Restart systemd service | sudo systemctl restart myapp |
Wrapping Up
The Maven build lifecycle covers everything from compiling and testing to packaging, and with a few shell commands (or a CI pipeline), you can take that artifact all the way to a running EC2 instance. For production workloads, running your app as a systemd service is worth the five-minute setup — you get automatic restarts, centralized logging via journalctl, and clean start/stop semantics.
From here, the natural next step is moving toward a more managed deployment target — ECS, Elastic Beanstalk, or a CodeDeploy-managed EC2 fleet — but this pipeline gets most teams very far, very fast.