# Ứng dụng Java

> Deploy Spring Boot, Quarkus và ứng dụng web Java khác bằng Maven hoặc Gradle, chỉnh heap JVM vừa với gói hosting, lắng nghe trên $PORT và xử lý lỗi khởi động.

Source: https://zopanel.net/vi/docs/java  
Updated: 2026-10-09

ZoPanel build ứng dụng web Java từ dự án Maven hoặc Gradle, chạy file jar thu được như một dịch vụ của tài khoản hosting và đặt nginx kèm SSL phía trước. Dùng cho Spring Boot, Quarkus hoặc bất kỳ HTTP server Java nào nhận được cổng lúc khởi động.

## Điều kiện cần

- Quản trị viên đã cài các phiên bản Java mà ứng dụng cần ở mục **Runtime**: thẻ **Java (OpenJDK + Maven)** có một nút cho mỗi phiên bản OpenJDK mà gói hệ thống cung cấp (ví dụ **OpenJDK 21**). Maven được cài cùng phiên bản đầu tiên. Có thể cài song song nhiều phiên bản:

  | Hệ điều hành | Các phiên bản OpenJDK có sẵn |
  | --- | --- |
  | Ubuntu 22.04, Ubuntu 24.04 | 17, 21, 25 |
  | Debian 12 | 17 |
  | Debian 13 | 21, 25 |

  Với phiên bản Java khác (ví dụ Java 21 trên Debian 12, hoặc Java 8), hãy deploy bằng Dockerfile.
- ZoPanel **không** cài Gradle: gói `gradle` của các bản phân phối quá cũ so với yêu cầu của dự án hiện nay. Dự án Gradle phải có Gradle wrapper (`gradlew` và thư mục `gradle/wrapper/`) trong repository.
- Gói hosting có tính năng **Git deploy & ứng dụng** và đủ **RAM (MB)** cho quá trình build và hai JVM (xem mục Bộ nhớ bên dưới).

## ZoPanel nhận diện gì

Dự án được coi là Java khi thư mục gốc có `pom.xml`, `build.gradle` hoặc `build.gradle.kts`. ZoPanel đề xuất các lệnh sau:

| Dự án | Lệnh build | Lệnh chạy |
| --- | --- | --- |
| Maven | `mvn -B -q -DskipTests package` (hoặc `sh mvnw …` khi có `mvnw`) | `java -Xmx512m -jar "<file jar đầu tiên trong target/>"` |
| Gradle | `sh gradlew --no-daemon -q build -x test` (bắt buộc có wrapper, xem bên dưới) | `java -Xmx512m -jar "<file jar đầu tiên trong build/libs/>"` |
| Spring Boot (Maven hoặc Gradle) | như trên; Gradle dùng task `bootJar` | như trên, thêm `--server.port=$PORT --server.address=127.0.0.1` |
| Quarkus (Maven hoặc Gradle) | như trên | `java -Xmx512m -Dquarkus.http.host=127.0.0.1 -Dquarkus.http.port=$PORT -jar target/quarkus-app/quarkus-run.jar` (`build/quarkus-app/…` với Gradle) |

File jar được chọn lúc khởi động bằng `ls`, bỏ qua các jar có tên kết thúc bằng `-plain.jar` (jar không kèm thư viện của Gradle), `-sources.jar`, `-javadoc.jar`, `-tests.jar` và `-original.jar`. Spring Boot được nhận ra khi `pom.xml` chứa `spring-boot` hoặc file build Gradle chứa `org.springframework.boot`; Quarkus được nhận ra khi `pom.xml` chứa `quarkus-maven-plugin` hoặc `io.quarkus`, hoặc file build Gradle chứa `io.quarkus`. Test được bỏ qua khi build.

Nếu không có `gradlew`, lệnh build đề xuất gọi `gradle` và deploy dừng với thông báo "this Gradle project has no Gradle wrapper…". Hãy chạy `gradle wrapper` trên máy của bạn, commit `gradlew` và `gradle/wrapper/`, rồi deploy lại.

Việc nhận diện chạy theo thứ tự cố định, nên hãy kiểm tra framework được nhận diện nếu repository trộn nhiều ngôn ngữ:

- Nếu cùng thư mục có `package.json` hoặc `composer.json`, dự án được nhận là Node.js hoặc PHP. `Gemfile` cũng được xét trước Java.
- `Procfile` có dòng `web:` sẽ thay lệnh chạy.
- Với dự án nhiều module, đặt **Thư mục gốc** là module tạo ra ứng dụng web, hoặc tắt **Tự nhận diện** và tự viết lệnh.

Các framework khác như Micronaut được nhận là **Java** thông thường. Chúng build bình thường, nhưng hãy kiểm tra lệnh chạy (xem bên dưới).

### Phiên bản Java

ZoPanel đọc phiên bản Java mà dự án yêu cầu và dùng OpenJDK đó để build và chạy ứng dụng. `JAVA_HOME` và `PATH` trỏ tới JDK đó trong lúc build (Maven, Gradle wrapper) và khi ứng dụng chạy:

| Dự án | Đọc từ |
| --- | --- |
| Maven | `<java.version>`, `<maven.compiler.release>` hoặc `<maven.compiler.source>` trong `pom.xml` (`1.8` nghĩa là 8) |
| Gradle | `JavaLanguageVersion.of(…)`, `JavaVersion.VERSION_…` hoặc `sourceCompatibility` trong `build.gradle(.kts)` |

Log build ghi rõ lựa chọn ("Using OpenJDK 21"). Nếu phiên bản đó chưa được cài, hệ thống dùng JDK đã cài cũ nhất trong số các bản mới hơn, vì nó vẫn biên dịch và chạy được code cũ (dự án Java 8 và 11 build bằng OpenJDK 17). Nếu chỉ có JDK cũ hơn, deploy dừng với thông báo "Java 25 is not installed…": hãy cài phiên bản đó ở mục **Runtime**. Khi file build không ghi phiên bản, dùng JDK mới nhất đã cài. Muốn tự chọn, tắt **Tự nhận diện** và chọn **Phiên bản**. `JAVA_HOME` khai báo trong **Biến môi trường** sẽ ghi đè lựa chọn này.

## Deploy ứng dụng Java

1. Ở **Website → Thêm website**, chọn **Git deploy**, nhập tên miền và địa chỉ repository. Xem [Git deploy](/vi/docs/git-deploy) cho repository riêng tư và webhook.
2. Mở tab **Deploy** của website. Kiểm tra **Framework** và các lệnh trong **Build & chạy**.
3. Muốn sửa lệnh, tắt **Tự nhận diện**, sửa **Lệnh build** hoặc **Lệnh chạy**, rồi bấm **Lưu & deploy lại**.
4. Thêm cấu hình trong **Biến môi trường** (URL database, secret, profile).
5. Bấm **Deploy ngay** và theo dõi quá trình build trong log tác vụ.

Bản mới chỉ được đưa lên khi trả lời **Đường dẫn health check** (mặc định `/`) với mã dưới 500 trong vòng 90 giây. Trong lúc đó bản cũ vẫn phục vụ khách truy cập. Lệnh cài đặt và lệnh build mỗi lệnh được chạy tối đa 30 phút.

## Cổng và địa chỉ

Ứng dụng phải lắng nghe tại `127.0.0.1`, trên cổng nằm trong biến môi trường `PORT`. ZoPanel đặt `PORT` mỗi lần khởi động và giá trị này khác nhau giữa hai slot blue/green (slot thứ hai dùng cổng gốc cộng 10000), nên đừng ghi cứng số cổng.

**Spring Boot.** Lệnh chạy đề xuất đã truyền `--server.port=$PORT --server.address=127.0.0.1`. Bạn cũng có thể khai báo trong `application.properties`:

```properties
server.port=${PORT}
server.address=127.0.0.1
# nginx gửi X-Forwarded-For và X-Forwarded-Proto: dùng chúng cho redirect và IP khách
server.forward-headers-strategy=native
```

**Quarkus.** Bản build Quarkus tạo `target/quarkus-app/quarkus-run.jar`, thư viện nằm trong `target/quarkus-app/lib/` (`build/quarkus-app/` với Gradle). Lệnh chạy đề xuất chạy file này và truyền địa chỉ, cổng:

```bash
java -Xmx512m -Dquarkus.http.host=127.0.0.1 -Dquarkus.http.port=$PORT -jar target/quarkus-app/quarkus-run.jar
```

Nếu bạn đóng gói thành uber-jar (`quarkus.package.jar.type=uber-jar`), hãy tắt **Tự nhận diện** và trỏ lệnh chạy tới file đó.

**Server khác.** Đọc biến trong code, ví dụ:

```java
int port = Integer.parseInt(System.getenv("PORT"));
```

## Bộ nhớ

Mọi thứ tài khoản chạy đều dùng chung **RAM (MB)** của gói: PHP, cron job, quá trình build Maven/Gradle và các JVM. Tiến trình vượt giới hạn sẽ bị kernel dừng, sau đó ZoPanel khởi động lại ứng dụng.

- Lệnh chạy đề xuất đặt heap 512 MB bằng `-Xmx512m`. JVM dùng nhiều hơn heap (metaspace, stack của thread, code cache), nên tiến trình luôn lớn hơn giá trị `-Xmx`.
- Khi deploy, quá trình build chạy trong lúc bản hiện tại vẫn phục vụ, và trong một thời gian ngắn bản mới và bản cũ chạy song song. Hãy tính RAM của gói đủ cho hai JVM cùng chạy, cộng thêm phần dư.
- Muốn đổi heap, tắt **Tự nhận diện** và sửa `-Xmx` trong **Lệnh chạy**. Gói nhỏ thì giảm; chỉ tăng khi gói đủ chỗ cho gấp đôi giá trị đó.

Theo dõi RAM của tài khoản ở mục **Tài nguyên**, và RAM của ứng dụng ở thẻ trạng thái trong tab **Deploy**. Xem [Gói hosting và giới hạn](/vi/docs/packages-limits).

## Biến môi trường

Các biến trong **Biến môi trường** dùng được khi build và khi chạy. ZoPanel đặt thêm `PORT` và `HOST=127.0.0.1`.

- Tên biến gồm chữ, số và `_`, bắt đầu bằng chữ hoặc `_`, tối đa 64 ký tự. Giá trị nằm trên một dòng, tối đa 8.192 ký tự. Tối đa 200 biến.
- Giá trị được lưu trong file chỉ root đọc được và được systemd truyền cho dịch vụ, không bao giờ nằm trên dòng lệnh.

Spring Boot đọc biến môi trường theo cơ chế relaxed binding: `SPRING_DATASOURCE_URL` đặt `spring.datasource.url`, `SPRING_PROFILES_ACTIVE=prod` chọn profile. Quarkus đọc các biến như `QUARKUS_DATASOURCE_JDBC_URL` theo cách tương tự.

Database tạo trong ZoPanel dùng host `127.0.0.1`. Xem [Database](/vi/docs/databases).

## File và dữ liệu cố định

Ứng dụng chạy trong sandbox: chỉ ghi được trong thư mục home của tài khoản và thư mục `/tmp` riêng. Mỗi lần deploy tạo một thư mục release mới, nên file upload và dữ liệu cần giữ qua các bản phải nằm trong **Đường dẫn lưu trữ cố định** (ví dụ `uploads`) hoặc trong database. 5 bản release gần nhất được giữ lại để **Rollback**.

## Log

- **Log ứng dụng** trong tab **Deploy** hiển thị 400 dòng stdout và stderr mới nhất. Bật **Trực tiếp** để theo dõi liên tục.
- Toàn bộ output Maven/Gradle của từng lần deploy nằm trong log tác vụ ở mục **Tác vụ**.
- Access log và error log của nginx nằm ở tab **Nhật ký** của website.

## Xử lý sự cố

| Thông báo hoặc hiện tượng | Cách xử lý |
| --- | --- |
| "Java is not installed — install it under Runtimes" | Server chưa có JDK nào. Nhờ quản trị viên cài một phiên bản trên thẻ **Java (OpenJDK + Maven)** ở mục **Runtime**. |
| "Java 25 is not installed (installed: …)" | Dự án yêu cầu Java mới hơn bản server đang có. Cài phiên bản đó ở mục **Runtime** nếu hệ điều hành có, hạ phiên bản Java của dự án, hoặc deploy bằng Dockerfile. |
| "this Gradle project has no Gradle wrapper…" | Commit Gradle wrapper (`gradlew`, `gradle/wrapper/`) vào repository. |
| "OpenJDK 21 is not packaged for this system" khi cài | Hệ điều hành không cung cấp phiên bản đó (xem bảng ở trên). Chọn phiên bản khác hoặc deploy bằng Dockerfile. |
| `UnsupportedClassVersionError` | Jar được biên dịch cho Java mới hơn JDK đang chạy nó, thường do phiên bản bị đặt tay. Bật lại **Tự nhận diện**, hoặc chọn đúng **Phiên bản**. |
| `Unable to access jarfile` với tên rỗng | Không có jar chạy được trong `target/` hoặc `build/libs/`: kiểm tra output build, hoặc ghi rõ jar trong lệnh chạy. |
| "The app did not respond on port … within 90s (it must listen on $PORT)" | Ứng dụng lắng nghe cổng cố định (thường là 8080) hoặc khởi động quá chậm. Lắng nghe trên `$PORT`, giảm việc lúc khởi động hoặc tăng CPU của gói. |
| "The app exited during startup" | Xem **Log ứng dụng**: thường do thiếu biến, không kết nối được database, hoặc không tìm thấy jar (`Unable to access jarfile`). |
| `java.lang.OutOfMemoryError: Java heap space` | Tăng `-Xmx`, trong phạm vi RAM của gói. |
| Ứng dụng tự khởi động lại mà không có lỗi Java | Tài khoản chạm giới hạn RAM. Giảm `-Xmx` hoặc tăng **RAM (MB)** của gói. |
| Redirect về `http://` hoặc ứng dụng thấy IP khách là `127.0.0.1` | Cho framework tin các header forwarded (`server.forward-headers-strategy=native` với Spring Boot). |

## Xem thêm

- [Ứng dụng Node.js và Python](/vi/docs/apps-node-python): quy trình deploy, cổng và phát hành blue/green
- [Git deploy](/vi/docs/git-deploy)
- [Deploy bằng Docker](/vi/docs/docker-deploy): khi cần JDK mà server không cung cấp
- [Gói hosting và giới hạn](/vi/docs/packages-limits)
- [Spring Boot: cấu hình bên ngoài](https://docs.spring.io/spring-boot/reference/features/external-config.html)
- [Quarkus: bắt đầu](https://quarkus.io/guides/getting-started)
