Tranh luận Maven và Gradle thường dừng ở khẩu vị: XML hay Kotlin, khai báo hay lập trình. Bài này bỏ qua phần đó và đo.

Tôi dựng cùng một dự án — 300 lớp Java, cùng hai phụ thuộc — trên cả hai công cụ, rồi bấm giờ.

Ba kịch bản, ba lần chạy mỗi kịch bản

  Maven   clean package        : 1075  983  909 ms
  Maven   package (không đổi)  : 1380 1292 1279 ms

  Gradle  clean build          : 2799  937  745 ms
  Gradle  build (không đổi)    :  378  356  375 ms

Ba điều đọc ra được, và cái thứ ba làm tôi phải chạy lại để tin.

Build sạch: hoà. Khoảng 950 ms cho Maven, 840 ms cho Gradle sau khi nóng máy. Khác biệt nằm trong khoảng nhiễu. Ai nói "Gradle build nhanh hơn hẳn" thì không nói về trường hợp này.

Build lại khi không đổi gì: Gradle nhanh hơn 3,5 lần. 370 ms so với 1280 ms. Đây mới là chỗ Gradle thắng, và nó thắng vì một lý do đơn giản: Gradle kiểm xem có gì đổi không và bỏ qua những tác vụ đã cập nhật, còn Maven thì biên dịch lại tất cả mỗi lần.

Và Maven build lại còn chậm hơn chính build sạch của nó — 1280 ms so với 950 ms, ổn định qua cả ba lần. Nghe vô lý nhưng có lý: clean package xoá sạch rồi làm mới, còn package phải xử lý cả những sản phẩm cũ đang nằm đó mà vẫn không tiết kiệm được gì.

Chú ý con số 2799 ms ở lần Gradle chạy đầu tiên. Đó là chi phí khởi động tiến trình nền. Từ lần thứ hai nó xuống 745–937 ms. Trên máy CI mà mỗi build là một container mới, bạn luôn trả cái giá 2799 ms ấy và không bao giờ hưởng lợi từ 370 ms.

Kết luận thực dụng: lợi thế tốc độ của Gradle nằm ở vòng lặp phát triển hằng ngày — sửa một chỗ, chạy test, lặp lại hàng trăm lần mỗi ngày. Trên CI chạy sạch mỗi lần, khác biệt gần như biến mất, và tiến trình nền còn thành gánh nặng.

Khác biệt nguy hiểm hơn: chọn phiên bản

Cùng hai phụ thuộc, cùng xung đột commons-codec ở bài hôm qua:

  Maven  : commons-codec:1.11              <- gần nhất thắng
  Gradle : commons-codec:1.11 -> 1.15      <- mới nhất thắng

Dấu mũi tên trong kết quả của Gradle nói rõ: nó thấy yêu cầu 1.11, và nâng lên 1.15.

Hai công cụ, cùng tệp khai báo về mặt nội dung, hai thư viện khác nhau lúc chạy.

Với tôi cách của Gradle hợp lý hơn — phiên bản mới thường tương thích ngược, và thư viện cần 1.15 sẽ có 1.15. Nhưng điểm chính không phải cái nào đúng: nếu bạn chuyển dự án từ Maven sang Gradle, tập phiên bản thư viện thực tế của bạn sẽ đổi, và không có gì trong quá trình chuyển đổi báo cho bạn biết.

Đây là thứ đáng kiểm ngay sau khi chuyển: xuất cây phụ thuộc của cả hai bên rồi so.

Mô hình khác nhau ở đâu

Maven khai báo: bạn mô tả dự án, Maven quyết định làm gì. Vòng đời cố định như bài 91, và mọi dự án Maven trên thế giới đều theo cùng chuỗi pha đó.

Gradle là đồ thị tác vụ: bạn định nghĩa các tác vụ và quan hệ giữa chúng.

plugins { id 'java' }
repositories { mavenCentral() }
dependencies {
    implementation 'org.apache.httpcomponents:httpclient:4.5.13'
}

Bảy dòng so với hai mươi dòng XML tương đương — đó là điểm hấp dẫn đầu tiên và ai cũng thấy.

Nhưng đánh đổi rất thật: tệp build của Gradle là mã, và mã thì làm được mọi thứ. Một pom.xml lạ vẫn đọc được trong ba mươi giây. Một build.gradle mà ai đó viết vòng lặp sinh tác vụ động, cộng ba plugin tự viết, thì phải hiểu cả Groovy lẫn vòng đời Gradle mới lần ra được chuyện gì đang xảy ra.

Tôi đã gặp cả hai loại dự án, và loại thứ hai tốn thời gian hơn nhiều.

Vài khác biệt cụ thể đáng biết:

implementation so với api. Gradle phân biệt phụ thuộc dùng nội bộ với phụ thuộc lộ ra trong API công khai. implementation không lan sang người dùng thư viện của bạn, nên build nhanh hơn và ranh giới rõ hơn. Maven không có khái niệm này — mọi thứ compile đều lan.

Kotlin DSL. build.gradle.kts cho gợi ý mã và kiểm kiểu trong IDE, thứ mà Groovy DSL không có. Với dự án mới tôi luôn chọn nó.

Bộ nhớ đệm build. Gradle chia sẻ được kết quả build giữa các máy, kể cả giữa CI và máy lập trình viên. Với dự án lớn nhiều mô-đun, đây là khác biệt lớn hơn mọi con số trong bài này.

Build song song nhiều mô-đun là mặc định của Gradle; Maven cần -T.

Tiêu chí chọn cho dự án mới

Tôi không nghĩ có câu trả lời chung, nhưng có vài tiêu chí khá rõ.

Chọn Maven khi: dự án là ứng dụng Java/Spring Boot cỡ vừa, một hoặc vài mô-đun; đội quen XML và không muốn học thêm; bạn ưu tiên "ai cũng đọc hiểu được" hơn tốc độ; hoặc dự án sẽ được đóng góp bởi nhiều người ngoài.

Chọn Gradle khi: dự án nhiều mô-đun và build mất hơn một phút; cần Android (Gradle là bắt buộc); cần logic build tuỳ biến thật sự; hoặc đội đã quen Gradle.

Đừng chuyển đổi chỉ vì tốc độ — bảng đầu bài cho thấy với dự án nhỏ, khác biệt là 900 mili giây. Chuyển đổi tốn hàng ngày công và mang theo rủi ro đổi phiên bản thư viện ở trên.

Một điều cả hai đều đúng: luôn dùng wrapper. ./mvnw./gradlew ghim phiên bản công cụ vào kho mã, nên build của bạn không đổi hành vi khi ai đó nâng cấp Maven hay Gradle trên máy họ. Với Gradle điều này còn quan trọng hơn vì các bản Gradle hay phá vỡ tương thích.

Nếu bạn phải làm việc với Gradle

Bốn lệnh tôi dùng nhiều nhất:

./gradlew tasks                      # liệt kê mọi tác vụ có sẵn
./gradlew dependencies --configuration runtimeClasspath
./gradlew build --scan               # báo cáo chi tiết, chỉ ra tác vụ nào tốn thời gian
./gradlew build --no-daemon          # tắt tiến trình nền, dùng trên CI

--scan là công cụ tôi thấy hữu ích nhất và ít người dùng: nó chỉ ra chính xác tác vụ nào chiếm thời gian, và tác vụ nào được bỏ qua nhờ bộ nhớ đệm. Với build chậm, nó thay thế mọi phỏng đoán.

Còn --no-daemon trên CI: tiến trình nền chỉ có ích khi nó sống qua nhiều lần build. Trong container dùng một lần, nó chỉ thêm chi phí khởi động — đúng con số 2799 ms ở đầu bài.

Thử ba mươi giây

Trên dự án của bạn, chạy build hai lần liên tiếp mà không sửa gì:

time ./mvnw package -DskipTests   # hoặc ./gradlew build
time ./mvnw package -DskipTests

Với Maven, hai con số gần bằng nhau — nó làm lại toàn bộ. Với Gradle, lần thứ hai ngắn hơn nhiều.

Nếu bạn chạy vòng lặp đó vài chục lần mỗi ngày, phép nhân sẽ cho bạn biết việc chuyển đổi có đáng hay không — chính xác hơn mọi lời khuyên trên mạng.

Ngày mai: viết test cho ra hồn với JUnit 5 — vòng đời, test theo tham số, và những khẳng định nói rõ vì sao chúng hỏng.