gradle init dla Spring Boot
Produkcyjny plugin Gradle inspirowany praktykami z DPE University.

Dlaczego kolejny plugin Gradle
Ostatnio chciałem poszerzyć swoją wiedzę o Build Scanach, Develocity i ich praktycznym zastosowaniu, dlatego zacząłem przerabiać kursy na naszej platformie DPE University. Na co dzień mam styczność z Develocity od strony operacyjnej i produkcyjnej – utrzymuję między innymi instancje open source, więc zależało mi na lepszym zrozumieniu produktu, a zwłaszcza użycia od strony użytkowników, w mojej codziennej pracy inżyniera.
Aby usystematyzować zdobytą wiedzę, postanowiłem zbudować projekt zgodny z best practices omawianymi na kursach. Równolegle pracowałem nad mikroserwisem w Spring Boot, generowanym ze Spring Initializr, i zauważyłem brak pluginu Gradle, który automatyzowałby ten proces w sposób przyjazny build cache i CI/CD. W efekcie każdy nowy projekt wymagał ręcznej konfiguracji. Chciałem to uprościć i zbliżyć doświadczenie do znanego gradle init – ale w wersji dla Spring Boot.
Tak powstał gradle-springinitializr-plugin – narzędzie gotowe do użycia na produkcji, które ułatwia tworzenie projektów Spring Boot z poziomu Gradle. Ten wpis jest o tym, jak powstał, jak działa i jak możesz go wykorzystać, aby przyspieszyć start nowych projektów w swoim zespole.
Plugin jest open source i dostępny w Gradle - Plugins. Możesz od razu go zainstalować i używać.
Stwórz plik build.gradle oraz dodaj poniższe:
| |
Następnie stwórz plik settings.gradle i dodaj konfigurację:
| |
Lub dodaj konfigurację do build.gradle w swoim istniejącym projekcie.
Dzięki temu będziesz mógł generować kolejne projekty Spring Boot z Gradle od razu podczas czytania wpisu, przyspieszając swój onboarding i codzienną pracę.
👉 GitHub
Ten plugin traktuję jako wzorzec: jak budować produkcyjny plugin w Groovy zgodny z Gradle best practices. Jeśli sam tworzysz pluginy, możesz go użyć jako punkt odniesienia.
Dla kogo
Dla Platform Engineerów
gradle-springinitializr-plugin może stać się elementem Waszej platformy, który przyspieszy tworzenie mikroserwisów w organizacji. Dzięki niemu możecie:
✅ Szybko generować mikroserwisy Spring Boot (Groovy/Java/Kotlin, Gradle/Maven) z CLI, pipeline’ów lub automatycznych procesów GitOps.
✅ Generować projekty zawsze z aktualną wersją Spring Boot, Javy i zależności, eliminując ręczne utrzymywanie szablonów.
✅ Standaryzować strukturę projektów, metadane (groupId, artifactId, packageName, description) i proces tworzenia mikroserwisów.
Jeśli budujecie platformę inżynierską dla zespołów developerskich w ekosystemie JVM, ten plugin może stać się Waszym lekko konfigurowalnym, automatyzowalnym elementem pipeline do bardzo prostego tworzenia projektów Spring Boot.
Dla Software Engineerów
gradle-springinitializr-plugin może stać się Waszym pierwszym wyborem do szybkiego startu projektów Spring Boot bez opuszczania ekosystemu Gradle. Zamiast klikania w webowe UI Spring Initializr czy pisania curli, generujecie w pełni konfigurowalny projekt jedną komendą Gradle. To oszczędność czasu, spójność konfiguracji i od razu wpięcie w Wasz workflow build cache, CI/CD oraz konwencje zespołowe.
Dla osób ciekawych i świadomych
gradle-springinitializr-plugin pozwoli Wam w jedną minutę zobaczyć, jak wygląda proces generowania projektu Spring Boot z poziomu Gradle, bez klikania po https://start.spring.io czy pisania curli. Zamiast ręcznego ściągania ZIP-ów, od razu startujecie projekt lokalnie i możecie eksplorować Spring Boot w sposób powtarzalny, czysty i przyjazny.
Jak działa
Po zainstalowaniu gradle-springinitializr-plugin możesz generować projekty Spring Boot bezpośrednio z Gradle, bez opuszczania terminala.
Plugin udostępnia pojedynczy task initSpringBootProject, który pobiera projekt z Spring Initializr i następnie rozpakowuje go. Zamiast klikać w interfejs webowy, wystarczy uruchomić:
| |
Otrzymasz:
| |
Domyślnie projekt zostanie pobrany i rozpakowany do build/generated-project/demo, gotowy do otwarcia i uruchomienia.
Plugin pozwala także ustawić dowolny parametr dostępny w Spring Initializr:
| |
Dzięki temu możesz generować czyste, spójne projekty, gotowe do pracy w Twoim workflow CI/CD i zgodne z zespołowymi konwencjami – bez manualnych kroków.
Plugin wspiera także eksperymentalny tryb interaktywny wzorowany na komendzie gradle init:
Dzięki temu możesz wprowadzić wszystkie parametry projektu bez konieczności zapamiętywania flag wiersza poleceń - plugin przeprowadza go krok po kroku przez proces wyboru wersji Spring Boot, języka, typu projektu i zależności. To szczególnie przydatne dla nowych członków zespołu lub w sytuacjach, gdy chcemy szybko przygotować projekt w standardzie naszej organizacji.
Aby skorzystać z trybu interaktywnego, należy podążać za instrukcjami z FAQ.
Jak został zbudowany
Jak wspomniałem wcześniej, poza wykorzystaniem wiedzy z kursów DPE University, chciałem, aby ten plugin był referencyjną implementacją pod budowę kolejnych pluginów. Moim celem było stworzenie czegoś solidnego, niemal bezobsługowego oraz tak bardzo ponadczasowego, jak to możliwe.
plugin.properties
Sercem pluginu jest plik plugin.properties, który zawiera domyślne wartości parametrów. To rozwiązanie wspiera zasadę separacji kodu oraz konfiguracji - zmiany wartości nie wymagają modyfikacji ani rekompilacji kodu, tylko podmiany pliku.
Plik properties jest ładowany raz przy starcie pluginu i wartości dostępne są poprzez statyczne stałe:
| |
Takie podejście upraszcza zarówno konfigurację produkcyjną, jak i testową – dając pełną kontrolę nad środowiskiem bez zmian w logice pluginu.
Aby plugin.properties był widoczny w testach funkcjonalnych jako zasób, należy dodać w build.gradle:
| |
Dzięki temu testy funkcjonalne korzystają z dokładnie tej samej konfiguracji co produkcyjny plugin.
convention: czysta konfiguracja pluginu
Opcje takie jak initializrUrl, metadataEndpoint i extract są zaprojektowane jako konfiguracja globalna pluginu – dotyczą źródła danych (czyli skąd pobierać projekt), a nie samego projektu (co ma zawierać). Dzięki temu plugin zachowuje separację między konfiguracją infrastrukturalną a właściwą logiką generowania.
Domyślne wartości są ustawiane przez mechanizm convention(...) w Gradle - co oznacza:
- użytkownik nie musi nic ustawiać, aby użyć pluginu w trybie domyślnym,
- ale może nadpisać dowolną wartość w
build.gradle, jeśli chce użyć np. własnej wersji Spring Initializr lub zablokować rozpakowanie ZIP-a, jak poniżej:
| |
To podejście zapewnia:
- czystość buildów - nie trzeba mieszać konfiguracji z parametrami wywołania.
- przewidywalność - plugin zawsze działa na jawnie zdefiniowanych danych wejściowych.
- elastyczność - użytkownik może użyć własnego endpointu, np. w CI albo mirrorze.
Zobacz oficjalną dokumentację Gradle dotyczącą convention(...), aby dowiedzieć się, jak ustawiać przejrzyste, nadpisywalne wartości domyślne we własnych pluginach i taskach.
Wyciąganie dostępnych opcji z Metadata API
Plugin pobiera wspierane wersje między innymi Spring Boot, typów projektu oraz języków, w których można programować aplikacje po pobraniu ze Spring Initializr. Jest to dostępne przy użyciu Metadata endpoint. Dzięki temu nie ma potrzeby ciągłego aktualizowania pluginu o wspierane parametry.
Podczas dodawania wspieranych typów projektów oraz języków natknąłem się na ciekawy problem z użyciem configuration cache w Gradle. O którym poniżej.
Na początku napotkałem kilka problemów podczas testowania pluginu z flagą --configuration-cache. Początkowo sądziłem, że problem wynika z odczytu plugin.properties, ale prawdziwą przyczyną było korzystanie z API project.* w klasie rozszerzającej DefaultTask:
| |
To łamie mechanizm configuration cache, ponieważ Gradle wymaga, aby wszystkie dane wejściowe były jawnie zadeklarowane przy użyciu Property<T>, Provider<T> lub DirectoryProperty. Następnie w fazie wykonania nie można odwoływać się do zmiennego stanu projektu.
Rozwiązałem to przez:
Przeniesienie całej logiki pobierania wspieranych typów projektu i języków do metody
download().Upewnienie się, że
@TaskActionoperuje wyłącznie na właściwościach deklarowanych jakoProperty<T>lubProvider<T>, czyli ocenianych dopiero w czasie wykonania.
Ta zmiana nie tylko umożliwiła zgodność pluginu z configuration cache, ale też doprowadziła do czystszej, bardziej idiomatycznej implementacji zgodnej z praktykami Gradle.
Zobacz oficjalną dokumentację Gradle dotyczącą configuration cache, aby poznać wszystkie szczegóły i ograniczenia.
Parametry -P i mechanizm walidacji
Plugin akceptuje wszystkie parametry dostępne na stronie https://start.spring.io - te same, które możesz ustawić w webowym UI Spring Initializr.
Po ustawieniu przez użytkowników parametrów albo przypisania domyślnych wartości, w tasku initSpringBootProject jest robiona walidacja. Task na początku pobiera listę wspieranych wersji Spring Boot, typów projektu oraz języków z endpointu Metadata. Jeśli użytkownik poda błędną wartość parametru, która nie jest wspierana task zakończy się błędem. Na przykład:
| |
Dostanie komunikat:
| |
Incremental build i build cache
Na podstawie ścieżki Gradle Build Caching na DPE University oraz wiedzy tam nabytej w pluginie zostało dodane wsparcie do incremental build oraz Build Cache. Dzięki temu kolejne uruchomienia są natychmiastowe, a rezultaty mogą być ponownie użyte.
Task initSpringBootProject deklaruje zarówno dane wejściowe (@Input), jak i dane wyjściowe (@OutputDirectory - w tym przypadku outputDir, powiązany z DirectoryProperty). To zgodne z definicją poprawnie skonfigurowanego taska w Gradle, opisaną tutaj.
Dzięki temu Gradle ma pełną kontrolę nad śledzeniem stanu wejść i wyjść, co umożliwia:
- Pomijanie taska (
UP-TO-DATE), jeśli dane wyjściowe się nie zmieniły - incremental build. - Przywrócenie wyników (
FROM-CACHE), jeśli dane wyjściowe są w lokalnym cache - build cache.
Poniżej znajduje się przykład, jak task zachowuje się w obu przypadkach.
Incremental build
Przy pierwszym uruchomieniu pobierany jest projekt ze Spring Initializr:
| |
Zostanie wyświetlone:
| |
Natomiast, przy ponownym uruchomieniu z tym samym parametrem -PoutputDir:
| |
Ponieważ dane wyjściowe się nie zmieniły, będzie widoczne:
| |
Co potwierdza, że wykorzystany jest incremental build przez oznaczenie taska etykietą UP-TO-DATE.
Build Cache
Gdy plik /opt/my-projects/my-spring-boot-app/starter.zip zostanie skasowany, a task zostanie uruchomiony ponownie:
| |
Z racji tego, że dane wyjściowe wcześniej zostały dodane do lokalnego cache, w odpowiedzi będzie widoczne poniższe:
| |
Co mówi o wykorzystaniu lokalnego build cache, potwierdzone przez oznaczenie FROM-CACHE.
Podczas przerabiania ścieżki Gradle Build Caching na DPE University zaciekawiło mnie, jak dokładnie działa lokalny build cache i co właściwie trafia do jego środka. Postanowiłem to rozłożyć na czynniki pierwsze.
Po wykonaniu taska initSpringBootProject uwagę zwraca klucz build cache: 75b4d5a65d989aa6453584fe39baeab5. To archiwum, które znajduje się w katalogu $GRADLE_USER_HOME/caches/build-cache-1. Po rozpakowaniu można zobaczyć m.in. plik METADATA, który zawiera następujące informacje:
| |
Natomiast, w folderze tree-outputDir/ znajduje się zarchiwizowany plik starter.zip oraz folder z projektem, czyli dokładnie to, co wcześniej zostało pobrane ze Spring Initializr i zapisane jako wynik działania taska.
Więcej o incremental build oraz Build Cache, możesz znaleźć w oficjalnej dokumentacji:
👉 Build Cache.
Tryb interaktywny
Aby zachować zgodność ze wsparciem incremental build oraz build cache zostało użyte odpowiednio upToDateWhen oraz cacheIf.
Gotowość do użytku
gradle-springinitializr-plugin nie jest tylko hobbystycznym projektem. Traktuję go bardzo poważnie, dlatego też oprócz dodania niezbędnych funkcjonalności zawiera on wszystkie kluczowe aspekty inżynierii oprogramowania, które opisane są w poniższych sekcjach.
Testy jednostkowe
Zostały napisane w Spocku. Pokrywają kluczowe funkcjonalności, takie jak walidację, budowanie zapytania do Spring Initializr oraz rozpakowywanie. Zgodnie z piramidą testów, jest ich najwięcej w projekcie.
Testy funkcjonalne
Również napisane w Spocku z użyciem Gradle TestKit. Do tego testuję odpowiedzi JSON ze Spring Initializr z użyciem WireMock zgodne z polecaną oficjalną dokumentacją ze Spring Initializr.
Na początku podczas pracy nad testami funkcjonalnymi natknąłem się na przypadek opisany w dokumentacji TestKit w paragrafie Controlling the build environment.
Mimo tego, że task initSpringBootProject wykonywał się i w logach widziałem:
| |
To testy nie przechodziły, a ja dostawałem poniższą wiadomość w logach:
| |
Problem z testem wynikał z tego, że Gradle TestKit zawsze uruchamia build w odizolowanym katalogu roboczym wewnątrz java.io.tmpdir (np. /private/var/folders/.../build/), a nie w katalogu, w którym oczekiwałem plików.
Natomiast w asercji (generatedProjectDir.absolutePath) szukałem plików w katalogu tymczasowym Spocka (@TempDir), który jest innym miejscem niż working directory TestKit.
Rozwiązałem to przez:
- Ustawienie
-PoutputDir=${generatedProjectDir.absolutePath}, które wymuszało, by plugin zapisał pliki tam, gdzie oczekiwał test.
Statyczna analiza kodu
Każde narzędzie przed wypuszczeniem do użytku powinno posiadać mechanizm statycznej analizy kodu. W tym przypadku został użyty CodeNarc plugin z koniecznym zestawem reguł przydatnych dla narzędzi typu Gradle plugin.
Pokrycie kodu testami
Tak, jak zostało wcześniej wspomniane gradle-springinitializr-plugin jest objęty zarówno testami jednostkowymi, jak i funkcjonalnymi. Pokrycie jest mierzone przy pomocy JaCoCo plugin. Obejmuje oba rodzaje testów i musi przekraczać 80%.
Ciągła integracja
Do ciągłej integracji zostało wykorzystane GitHub Actions. Przy każdym PR weryfikowane jest, czy commit jest zgodny z Conventional Commits. Następnie wykonywane jest gradle build. Dzięki temu sprawdzane jest poniższe:
- Kompilacja kodu
- Testy jednostkowe oraz funkcjonalne
- Statyczna analiza kodu
- Pokrycie kodu testami
Build Scan
Na końcu procesu ciągłej integracji, Build Scan z wykonaniem jest publikowany w https://scans.gradle.com. Przykładowy Build Scan z wykonania można znaleźć tutaj.
Testy kompatybilności
W procesie tworzenia pluginu ważne było, żeby był kompatybilny z różnymi wersjami Gradle oraz Kotlin DSL i Groovy DSL. Dlatego też przy każdym PR oprócz wcześniej wspomnianych kroków są uruchamiane właśnie testy kompatybilności, które uruchamiają task initSpringBootProject na różnych wersjach Gradle z katalogów:
examples/simple-groovyexamples/simple-kotlin
To wszystko jest możliwe dzięki wykorzystaniu strategii matrix w GitHub Actions. Więcej o tym można znaleźć tutaj.
Ciągłe dostarczanie
Po wcześniej opisanym procesie ciągłej integracji, gdy commit trafia do main’a uruchamiane jest GitHub Actions, które wykorzystuje semantic-release do zarządzania zmianami w CHANGELOG.md, tworzenia nowego taga wraz z releasem w repozytorium oraz publikowania pluginu do Gradle - Plugins.
Podsumowanie
Tworząc gradle-springinitializr-plugin miałem nie tylko radość z odwzorowania gradle init dla Spring Boot, przerabiania kursów DPE University poznania API Spring Initializr, najlepszych praktyk Gradle, Spock i Groovy, ale jestem w pełni zadowolony z efektu końcowego. To nie jest przykład kolejnego pluginu. To narzędzie gotowe do użycia lokalnie i jako element platformy przygotowane świadomie, solidnie i podążające za najlepszymi wzorcami.
Dla Platform Engineerów to gotowy asset do platformy, dla Software Engineerów – szybki start w codziennej pracy, a dla eksplorujących – praktyczny wzorzec budowy pluginu Gradle.
Zainteresowany rozwojem pluginu?
👉 Zobacz, jak możesz uczestniczyć w jego rozwoju i przejrzyj plik CONTRIBUTING.
Masz pomysł na nową funkcję lub znalazłeś buga?
👉 Zgłoś swój pomysł albo zaraportuj błąd tutaj.

