Gradle: как собирать Java- и Kotlin-проекты — от первой задачи до Spring Boot
Знакомая сцена. Вы открыли туториал по Java, дошли до места, где нужна сторонняя библиотека. Идёте на сайт, качаете JAR-файл, кидаете в папку проекта. Запускаете — ошибка: библиотеке нужна ещё одна библиотека, которой нет. Качаете вторую. Ей нужна третья. К пятому файлу вы уже не помните, какой версии что скачали и зачем.
На следующий день к проекту подключается коллега. У него другая версия одной из библиотек, и код, который у вас работал, у него падает. Классическое «у меня же работает» — а на деле просто разные JAR-файлы лежат на двух машинах.
Gradle убирает этот хаос целиком. Вы не качаете JAR-файлы руками — вы пишете одну строку с названием и версией библиотеки, а Gradle сам скачивает её и всё, что ей нужно, нужных версий. Коллега клонирует проект, запускает одну команду — и получает ровно ту же сборку, что у вас. Это разница между «я полдня настраивал окружение» и «склонировал, собрал, работаю».
Разберём по шагам: структура проекта, файл сборки, задачи, зависимости, плагины и wrapper. В конце соберём реальное приложение на Spring Boot одной командой.
Gradle — обязательный инструмент Java-разработчика. Он входит в программы «Java-разработчик» и курсы по Spring Boot на Хекслете.
Что такое Gradle и зачем он нужен
Gradle — система сборки. Её работа — превратить ваш исходный код и список зависимостей в готовый артефакт: JAR-файл, который можно запустить или выложить. По дороге она скачивает библиотеки, компилирует код, прогоняет тесты, упаковывает результат.
Без системы сборки всё это делается руками: качаем библиотеки, вызываем компилятор javac с длинным списком путей, собираем JAR утилитой jar. На учебном примере из двух классов это терпимо. На реальном проекте с полусотней зависимостей — невозможно.
В мире Java два главных инструмента сборки: Maven и Gradle. Maven старше и описывает сборку через XML. Gradle новее, описывает сборку через короткий код на Groovy или Kotlin и работает быстрее за счёт кеша и инкрементальной сборки. Под Android Gradle — единственный официальный вариант, поэтому для мобильной разработки альтернативы нет.
Что делаем | Руками | С Gradle |
|---|---|---|
Подключить библиотеку | Скачать JAR и все его зависимости | Одна строка с названием и версией |
Скомпилировать |
|
|
Прогнать тесты | Запускать вручную | Входит в сборку |
Собрать JAR | Утилита | Задача |
Повторить у коллеги | Надеяться на те же версии | Та же сборка через wrapper |
Структура проекта
Gradle ждёт определённого расположения файлов. Это соглашение, а не жёсткое правило, но следовать ему стоит — тогда всё работает без лишней настройки.
my-app/
├── build.gradle # описание сборки: плагины, зависимости, задачи
├── settings.gradle # имя проекта и состав модулей
├── gradlew # wrapper для Linux и macOS
├── gradlew.bat # wrapper для Windows
├── gradle/
│ └── wrapper/ # закреплённая версия Gradle
├── src/
│ ├── main/
│ │ ├── java/ # исходный код приложения
│ │ └── resources/ # конфигурации, шаблоны, статика
│ └── test/
│ ├── java/ # код тестов
│ └── resources/ # данные для тестов
└── build/ # результат сборки — генерируется, в git не коммитят
Главное правило: код приложения лежит в src/main/java, тесты — в src/test/java. Gradle сам найдёт оба каталога и будет компилировать их раздельно. Папку build создаёт сама сборка — её добавляют в .gitignore, потому что она пересобирается из исходников в любой момент.
Для Kotlin меняется только имя каталога — src/main/kotlin. Остальное то же самое. Gradle одинаково собирает оба языка, и в одном проекте они уживаются вместе.
build.gradle: сердце сборки
Весь проект описывается в одном файле — build.gradle. Это не конфигурация в привычном смысле, а код: набор блоков, каждый из которых отвечает за свою часть. Разберём минимальный рабочий файл для Java-приложения.
plugins {
id 'java' // компиляция Java, задачи build, test, jar
id 'application' // добавляет задачу run
}
group = 'io.hexlet'
version = '1.0.0'
repositories {
mavenCentral() // откуда качать библиотеки
}
dependencies {
implementation 'com.google.guava:guava:33.0.0-jre'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}
application {
mainClass = 'io.hexlet.App' // класс с методом main
}
tasks.named('test') {
useJUnitPlatform() // запускать тесты через JUnit 5
}
Четыре блока решают почти всё. plugins подключает готовые наборы задач. repositories говорит, откуда брать библиотеки. dependencies перечисляет сами библиотеки. Остальное — настройки: имя группы, версия, главный класс. Этот файл — полноценная программа, и при сборке Gradle его выполняет, а не просто читает.
Зависимость — это три части
Строка зависимости устроена единообразно: группа, имя, версия, разделённые двоеточием.
implementation 'com.google.guava:guava:33.0.0-jre'
// └── группа ──┘ └─имя─┘ └── версия ──┘По этим трём координатам Gradle находит библиотеку в репозитории и скачивает. Группа — это обычно домен организации наоборот, имя — название артефакта, версия — конкретный выпуск. Менять версию — значит поправить одно число в одной строке, а не перекачивать файлы.
Задачи: из чего собирается сборка
Всё, что делает Gradle, — это задачи. Задача (task) — отдельная единица работы: скомпилировать код, прогнать тесты, упаковать JAR. Сборка — это цепочка задач, выполненных в правильном порядке.
Посмотреть доступные задачи можно командой:
./gradlew tasksСамые частые задачи, которые вы будете запускать каждый день:
Команда | Что делает |
|---|---|
| Полная сборка: компиляция, тесты, упаковка JAR |
| Только прогон тестов |
| Запустить приложение (плагин application) |
| Удалить папку build — сборка с нуля |
| Собрать JAR без прогона тестов |
| Показать дерево всех зависимостей |
Задачи связаны между собой зависимостями. Когда вы запускаете build, Gradle не выполняет всё подряд — он строит граф: чтобы собрать JAR, нужны скомпилированные классы; чтобы их получить, нужно запустить компиляцию. Порядок Gradle вычисляет сам.
compileJava → processResources → classes → jar
↓
test → check → build
Отсюда важное следствие: если тесты упали, задача build не дойдёт до конца. Сломанный код не превратится в JAR — сборка остановится на красном тесте. Это та же логика, что защищает прод в CI.
Зависимости и области видимости
Зависимости перечисляются в блоке dependencies, но перед каждой стоит слово, которое легко проскочить, — и зря. Оно задаёт область видимости: где библиотека доступна и попадает ли она к тем, кто будет использовать ваш код.
Область | Где доступна | Пример |
|---|---|---|
| Компиляция и запуск, но скрыта от тех, кто подключает ваш модуль | Большинство библиотек |
| То же, но видна и тем, кто подключает ваш модуль | Когда пишете библиотеку для других |
| Только при компиляции, в готовый JAR не попадает | Lombok, аннотации |
| Только при запуске, не нужна для компиляции | Драйвер базы данных |
| Только в тестах | JUnit, Mockito |

Разница между implementation и api — не формальность. В большом проекте из нескольких модулей implementation прячет внутренние библиотеки от соседних модулей. Это ускоряет пересборку: поменяли скрытую зависимость — пересобрался только её модуль, а не половина проекта. По умолчанию берите implementation, а api — только когда осознанно хотите пробросить библиотеку наружу.
Транзитивные зависимости
Здесь Gradle экономит больше всего сил. Вы указываете одну библиотеку, а она тянет за собой свои — те самые, за которыми в начале статьи приходилось бегать вручную. Gradle разворачивает всю цепочку сам.
// вы написали одну строку:
implementation 'org.springframework.boot:spring-boot-starter-web'
// а Gradle подтянул десятки библиотек:
// spring-web, spring-webmvc
// tomcat-embed-core (встроенный сервер)
// jackson-databind (работа с JSON)
// ... и зависимости каждой из нихЧто будет, если две разные библиотеки требуют одну и ту же третью, но разных версий. Gradle разрешает конфликт сам — по умолчанию берёт версию повыше. Увидеть итоговое дерево и понять, кто что притянул, помогает ./gradlew dependencies. Это первое, куда стоит смотреть, когда сборка ругается на несовместимые версии.
Плагины: готовые наборы возможностей
Плагин — это упакованный набор задач и настроек, который подключается одной строкой. Без плагина java Gradle не знал бы, как компилировать Java; именно плагин добавляет задачи compileJava, test, jar.
Плагин | Что даёт |
|---|---|
| Компиляция Java, базовые задачи сборки и тестов |
| Задачу |
| Сборку исполняемого JAR со встроенным сервером, задачу |
| Согласованные версии библиотек Spring — можно не писать версии вручную |
| Проверку стиля кода и замер покрытия тестами |
Связка плагинов Spring Boot — отдельная удача для новичка. Плагин управления зависимостями знает, какие версии библиотек Spring совместимы между собой, и подставляет их сам. Поэтому в проекте на Spring Boot у зависимостей часто нет номера версии — её определяет плагин:
dependencies {
// версии нет — её подставит плагин Spring Boot
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}Groovy или Kotlin: два диалекта файла сборки
Файл сборки бывает в двух вариантах. build.gradle написан на Groovy, build.gradle.kts — на Kotlin. Делают они одно и то же, синтаксис чуть разный.
// Groovy — build.gradle
implementation 'com.google.guava:guava:33.0.0-jre'
// Kotlin — build.gradle.kts
implementation("com.google.guava:guava:33.0.0-jre")Groovy короче и долго был вариантом по умолчанию — много примеров в интернете именно на нём. Kotlin строже: редактор кода знает типы, подсказывает названия задач и подсвечивает опечатку до запуска сборки, а не после. В новых проектах команда Gradle рекомендует Kotlin, и большинство свежих проектов на нём. Если только начинаете — берите Kotlin, особенно если уже пишете на нём код. Если разбираете чужой проект на Groovy — принципы те же, отличается только запись.
Gradle Wrapper: почему везде ./gradlew
В командах выше стоит ./gradlew, а не gradle. Это не опечатка. gradlew — wrapper, маленький скрипт в корне проекта, который сам скачивает нужную версию Gradle и запускает сборку через неё.
Зачем это нужно. Версия Gradle закреплена прямо в проекте, в файле gradle/wrapper/gradle-wrapper.properties:
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zipЛюбой, кто склонирует проект, соберёт его той же версией Gradle, что и автор, — даже если у него вообще не установлен Gradle. Wrapper скачает нужную версию сам. Это закрывает целый класс ошибок «у меня собирается, у тебя нет из-за разных версий». Поэтому правило простое: внутри проекта всегда вызывают ./gradlew, а не глобальный gradle. На Windows команда выглядит как gradlew.bat.
Файлы wrapper коммитят в репозиторий. Начинающие иногда добавляют gradlew, gradlew.bat и папку gradle/wrapper в .gitignore вместе с папкой build. Это ошибка: без них wrapper не работает, и весь смысл воспроизводимой сборки теряется. Из-под версионного контроля убирают только папку build.
Почему Gradle быстрый
Первая сборка проекта идёт небыстро — Gradle качает зависимости и компилирует всё с нуля. Зато вторая и последующие летают, и за этим стоят три механизма.
Инкрементальная сборка. Gradle помнит, что собирал в прошлый раз. Если файл не менялся, задачу для него не запускают повторно — в выводе такая задача помечается как UP-TO-DATE. Поменяли один класс — пересоберётся только он и то, что от него зависит.
Кеш сборки. Результаты задач сохраняются и переиспользуются. Если такую задачу с такими же входными данными уже собирали — результат берётся из кеша, метка FROM-CACHE. Кеш можно сделать общим на команду или на CI, и тогда чужую уже собранную часть не пересобирают.

Демон. Gradle держит в фоне процесс, который не выгружается между запусками. Виртуальная машина Java не стартует каждый раз заново, и команды отрабатывают заметно быстрее.
На практике это выглядит так: первый ./gradlew build идёт, скажем, полминуты, а повторный после правки одного файла — пару секунд. Большая часть задач в выводе будет с пометкой UP-TO-DATE.
Практика: собираем Spring Boot приложение
Соберём с нуля минимальное веб-приложение. Понадобится только установленный JDK — сам Gradle подтянет wrapper.
Шаг 1. Структура. Создаём каталоги и файл сборки:
demo/
├── build.gradle
├── settings.gradle
└── src/main/java/io/hexlet/
├── App.java
└── HelloController.javaШаг 2. settings.gradle — имя проекта:
rootProject.name = 'demo'Шаг 3. build.gradle — плагины и зависимости:
plugins {
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.4'
id 'java'
}
group = 'io.hexlet'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}Шаг 4. Код. Точка входа и один контроллер:
// App.java
package io.hexlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
// HelloController.java
package io.hexlet;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/")
public String hello() {
return "Привет от Gradle и Spring Boot";
}
}Шаг 5. Запуск. Одна команда — Gradle скачает Spring Boot со всеми транзитивными зависимостями, скомпилирует код и поднимет встроенный сервер:
./gradlew bootRunОткройте http://localhost:8080 — увидите ответ контроллера. Вы не качали ни одного JAR-файла руками: всё, что нужно Spring Boot, Gradle вытащил по двум строкам в блоке зависимостей. Тот же проект соберётся у любого коллеги одной командой — благодаря wrapper, той же версией Gradle.
Антипаттерны
Коммитить папку build. Это сгенерированные файлы, которые пересобираются из исходников за секунды. В репозитории они только раздувают историю и порождают конфликты при слиянии. Папка build всегда в .gitignore.
Прописывать версии библиотек врассыпную. Одна версия в одном месте файла, другая — в другом, в Spring-проекте вообще вручную там, где их подставляет плагин. Несовпадения версий одной библиотеки — частая причина странных ошибок. Держите версии в одном месте и доверяйте плагину управления зависимостями, где он есть.
Вызывать глобальный gradle вместо ./gradlew. У вас установлена одна версия, у коллеги другая, на CI третья — и сборка ведёт себя по-разному в трёх местах. Внутри проекта всегда wrapper.
Ставить всё подряд в implementation. Драйвер базы данных нужен только при запуске — это runtimeOnly. JUnit нужен только тестам — это testImplementation. Когда всё свалено в implementation, лишние библиотеки попадают в готовый JAR и в зависимости тех, кто подключит ваш модуль.
Игнорировать вывод dependencies при конфликте версий. Сборка ругается на несовместимость, а разработчик наугад меняет версии в build.gradle. Сначала смотрят дерево через ./gradlew dependencies — оно показывает, кто притянул проблемную библиотеку и какой версии.
FAQ
Что выбрать новичку — Gradle или Maven?
Оба живые и востребованные. Maven проще на старте и его XML предсказуем, Gradle гибче и быстрее. Под Android выбора нет — только Gradle. Для бэкенда на Spring многие команды берут Gradle за скорость и краткость файла сборки. Освоив один, второй понимают за вечер — идеи общие.
Нужно ли устанавливать Gradle отдельно?
Для работы с готовым проектом — нет, хватает JDK и wrapper в проекте. Глобальная установка Gradle нужна разве что для создания нового проекта с нуля командой gradle init. Дальше всё идёт через ./gradlew.
Чем отличаются build.gradle и settings.gradle?
build.gradle описывает, как собирать: плагины, зависимости, задачи. settings.gradle описывает структуру: имя проекта и какие модули в него входят. В простом проекте из одного модуля settings.gradle содержит всего одну строку с именем.
Почему первая сборка такая долгая?
Gradle скачивает wrapper нужной версии, все зависимости с их транзитивными библиотеками и компилирует код с нуля. Скачанное кешируется на машине, поэтому следующие сборки быстрее в разы — большинство задач отметятся как UP-TO-DATE.
Как обновить версию библиотеки?
Поправить номер версии в строке зависимости в build.gradle и пересобрать. Gradle скачает новую версию и пересчитает дерево транзитивных зависимостей. Если новая версия конфликтует с другими — это покажет ./gradlew dependencies.
Можно ли в одном проекте смешивать Java и Kotlin?
Да. Подключаете оба плагина, кладёте Java в src/main/java, Kotlin в src/main/kotlin, и Gradle собирает оба языка вместе. Классы видят друг друга. Это частый сценарий, когда проект постепенно переезжает с Java на Kotlin.






