Deploying Java and Spring Boot Applications on DomainIndia VPS
Why VPS, not shared
Java apps are long-running JVM processes consuming 256 MB–2 GB+ RAM. Shared cPanel/DirectAdmin optimises for PHP per-request workloads — the JVM doesn't fit. A DomainIndia VPS gives you the root access, persistent process control, and RAM you need.
Minimum VPS for Spring Boot production:
| App size | VPS plan | JVM heap | Concurrency |
|---|---|---|---|
| Small API / MVP | VPS Starter (2 GB) | -Xmx512m | ~50 req/sec |
| Production API | VPS Business (4 GB) | -Xmx2g | ~200 req/sec |
| Enterprise / microservices | VPS Enterprise (8+ GB) | -Xmx4g per service | 1000+ req/sec |
Step 1 — Install JDK
Java 21 LTS is the current long-term-support release (2026).
- SSH into VPS as root
- Install OpenJDK 21:
```bash
# AlmaLinux / Rocky
sudo dnf install -y java-21-openjdk-devel
# Ubuntu 22.04+
sudo apt install -y openjdk-21-jdk
```
- Verify:
java -version→ should showopenjdk version "21..." - Create app user:
```bash
sudo useradd -r -s /bin/false -m -d /opt/spring-app spring
```
Use LTS versions only. Java 21 is LTS (support until 2031). Java 22/23 are non-LTS — fine for experiments, risky for production. Our OpenJDK packages include security updates via standard OS updates.
Step 2 — Build your Spring Boot JAR
On your laptop:
./mvnw clean package -DskipTests
# Produces target/your-app-0.0.1-SNAPSHOT.jarUpload:
scp target/your-app-0.0.1-SNAPSHOT.jar root@your-vps:/opt/spring-app/app.jar
sudo chown spring:spring /opt/spring-app/app.jarStep 3 — systemd service
Create /etc/systemd/system/spring-app.service:
[Unit]
Description=Spring Boot Application
After=network.target postgresql.service
[Service]
Type=simple
User=spring
Group=spring
WorkingDirectory=/opt/spring-app
ExecStart=/usr/bin/java
-Xms256m -Xmx1024m
-XX:+UseG1GC
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/opt/spring-app/heapdumps
-Dspring.profiles.active=production
-Dserver.port=8080
-jar /opt/spring-app/app.jar
SuccessExitStatus=143
TimeoutStopSec=20
Restart=on-failure
RestartSec=10
# Security
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/spring-app
ProtectHome=true
# Environment
EnvironmentFile=-/opt/spring-app/.env
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.targetCreate /opt/spring-app/.env:
DB_URL=jdbc:postgresql://localhost/myapp
DB_USER=spring
DB_PASSWORD=changeme
JWT_SECRET=some-random-256-bit-stringStart:
sudo systemctl daemon-reload
sudo systemctl enable --now spring-app
sudo journalctl -u spring-app -fStep 4 — nginx reverse proxy
/etc/nginx/conf.d/spring.conf:
upstream spring {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name api.yourcompany.com;
client_max_body_size 20M;
location / {
proxy_pass http://spring;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
proxy_read_timeout 60s;
}
}Test + reload + Let's Encrypt:
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d api.yourcompany.comStep 5 — Database (PostgreSQL)
sudo dnf install -y postgresql-server postgresql-contrib
sudo postgresql-setup --initdb
sudo systemctl enable --now postgresql
sudo -u postgres psql -c "CREATE DATABASE myapp;"
sudo -u postgres psql -c "CREATE USER spring WITH ENCRYPTED PASSWORD 'changeme';"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE myapp TO spring;"application-production.yml in your Spring app:
spring:
datasource:
url: ${DB_URL}
username: ${DB_USER}
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: validate # or update for dev; never create in prod
show-sql: false
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialectMemory tuning
Wrong heap sizing is the #1 cause of Spring Boot crashes on small VPS.
| VPS RAM | -Xms | -Xmx | OS overhead buffer |
|---|---|---|---|
| 2 GB | 256m | 1024m | ~1 GB |
| 4 GB | 512m | 2048m | ~2 GB |
| 8 GB | 1024m | 4096m | ~4 GB |
Rule: JVM max heap ≤ (total RAM / 2). The JVM uses non-heap memory (metaspace, native code, threads) that's not counted in -Xmx. Over-sized heap + OOM killer = service restart loop.
Enable XX:+HeapDumpOnOutOfMemoryError so when it does crash, you have a dump to debug with Eclipse MAT.
Build optimisation
Multi-module projects with Maven:
./mvnw -T 4 clean package -DskipTests -Dspring-boot.build-image.skip=true-T 4 uses 4 parallel threads.
Spring Boot layered JAR (faster Docker rebuilds):
<!-- pom.xml -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layers><enabled>true</enabled></layers>
</configuration>
</plugin>Unpack the JAR into layers, containerise each layer separately → dependency layers cached, app layer small and fast.
Common pitfalls
FAQ
OpenJDK for production stability. Oracle JDK requires a commercial licence for business use. GraalVM Community Edition is interesting for low-RAM workloads (native-image reduces to ~30 MB + <100 ms startup), but builds are complex — worth exploring for microservices, overkill for monoliths.
Native: faster cold start, 5–10× less RAM, harder to build (reflection issues, long compile). JVM: slower start (3–10s), higher RAM, easier build. Start with JVM, move to native only if cold-start or RAM is critical.
Spring Boot defaults to Tomcat — fine for 95% of apps. Netty is reactive/non-blocking (WebFlux). Undertow is lighter than Tomcat if you're on a 2 GB VPS and need to squeeze RAM.
Don't — let nginx handle SSL. Simpler to renew, simpler to tune, standard ops practice.
Yes — different ports + different systemd services + nginx server blocks per domain. Plan RAM carefully.
Java/Spring needs a proper VPS with 4+ GB RAM for production workloads. See VPS plans