Production Java processes should normally be managed by systemd, a container orchestrator, or another supervisor. Searching ps output and killing a matched PID is fragile: it can select the grep process, match multiple applications, or start a duplicate instance.

Create /etc/systemd/system/myapp.service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[Unit]
Description=My Java Application
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/java -Xms512m -Xmx512m -jar /opt/myapp/myapp.jar
SuccessExitStatus=143
Restart=on-failure
RestartSec=5
TimeoutStopSec=45
KillSignal=SIGTERM
EnvironmentFile=-/etc/myapp/myapp.env

[Install]
WantedBy=multi-user.target

Create a non-login service user, make application files readable but not writable by unrelated users, and keep secrets in a restricted environment file or secrets manager.

Load and use the service:

1
2
3
4
5
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl restart myapp
sudo systemctl status myapp
journalctl -u myapp -n 100 --no-pager

Systemd tracks the actual process, serializes lifecycle operations, captures logs, and can restart failures.

Graceful shutdown

A normal systemctl stop sends SIGTERM. The application should stop accepting traffic, finish or reject in-flight work, close database connections, and exit before TimeoutStopSec. Spring Boot handles SIGTERM gracefully when its shutdown settings and surrounding infrastructure are configured correctly.

Do not use kill -9 as the first action. SIGKILL prevents cleanup and can leave partial work or stale locks. Reserve it for a process that does not exit after the documented grace period.

Health verification

A process being alive does not mean it is ready. After restart, check a readiness endpoint:

1
2
3
4
5
6
7
for attempt in {1..30}; do
if curl --fail --silent http://127.0.0.1:8080/actuator/health/readiness; then
exit 0
fi
sleep 2
done
exit 1

Load balancers should remove the instance before shutdown and add it back only after readiness succeeds.

Shell-script fallback

If systemd is unavailable, use a PID file created when the process starts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env bash
set -euo pipefail

APP=/opt/myapp/myapp.jar
PIDFILE=/run/user/$(id -u)/myapp.pid
LOG=/var/log/myapp/application.log

if [[ -f "$PIDFILE" ]] && kill -0 "$(<"$PIDFILE")" 2>/dev/null; then
pid=$(<"$PIDFILE")
kill -TERM "$pid"
for _ in {1..30}; do
kill -0 "$pid" 2>/dev/null || break
sleep 1
done
if kill -0 "$pid" 2>/dev/null; then
echo "Application did not stop gracefully" >&2
exit 1
fi
fi

nohup java -jar "$APP" >>"$LOG" 2>&1 &
echo $! >"$PIDFILE"

Add locking if concurrent executions are possible, rotate logs, and verify startup health. This fallback is still less reliable than a supervisor, so migrate to systemd or an orchestrator when possible.