/
Блог Хекслета
/
Код
/

Gradle: сборка Java- и Kotlin-проектов — Хекслет

Gradle: сборка Java- и Kotlin-проектов — Хекслет

25 июня 2026 г.

9 минут
Gradle: сборка Java- и Kotlin-проектов — Хекслет

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 и все его зависимости

Одна строка с названием и версией

Скомпилировать

javac со списком путей

gradle build

Прогнать тесты

Запускать вручную

Входит в сборку

Собрать JAR

Утилита 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 не коммитят
gradle_01_structure.png

Главное правило: код приложения лежит в 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
}
gradle_02_anatomy.png

Четыре блока решают почти всё. plugins подключает готовые наборы задач. repositories говорит, откуда брать библиотеки. dependencies перечисляет сами библиотеки. Остальное — настройки: имя группы, версия, главный класс. Этот файл — полноценная программа, и при сборке Gradle его выполняет, а не просто читает.

Зависимость — это три части

Строка зависимости устроена единообразно: группа, имя, версия, разделённые двоеточием.

implementation 'com.google.guava:guava:33.0.0-jre'
//              └── группа ──┘ └─имя─┘ └── версия ──┘

По этим трём координатам Gradle находит библиотеку в репозитории и скачивает. Группа — это обычно домен организации наоборот, имя — название артефакта, версия — конкретный выпуск. Менять версию — значит поправить одно число в одной строке, а не перекачивать файлы.

Задачи: из чего собирается сборка

Всё, что делает Gradle, — это задачи. Задача (task) — отдельная единица работы: скомпилировать код, прогнать тесты, упаковать JAR. Сборка — это цепочка задач, выполненных в правильном порядке.

Посмотреть доступные задачи можно командой:

./gradlew tasks

Самые частые задачи, которые вы будете запускать каждый день:

Команда

Что делает

./gradlew build

Полная сборка: компиляция, тесты, упаковка JAR

./gradlew test

Только прогон тестов

./gradlew run

Запустить приложение (плагин application)

./gradlew clean

Удалить папку build — сборка с нуля

./gradlew jar

Собрать JAR без прогона тестов

./gradlew dependencies

Показать дерево всех зависимостей

Задачи связаны между собой зависимостями. Когда вы запускаете build, Gradle не выполняет всё подряд — он строит граф: чтобы собрать JAR, нужны скомпилированные классы; чтобы их получить, нужно запустить компиляцию. Порядок Gradle вычисляет сам.

compileJava  →  processResources  →  classes  →  jar
                                         ↓
                                       test  →  check  →  build
gradle_04_lifecycle.png

Отсюда важное следствие: если тесты упали, задача build не дойдёт до конца. Сломанный код не превратится в JAR — сборка остановится на красном тесте. Это та же логика, что защищает прод в CI.

Зависимости и области видимости

Зависимости перечисляются в блоке dependencies, но перед каждой стоит слово, которое легко проскочить, — и зря. Оно задаёт область видимости: где библиотека доступна и попадает ли она к тем, кто будет использовать ваш код.

Область

Где доступна

Пример

implementation

Компиляция и запуск, но скрыта от тех, кто подключает ваш модуль

Большинство библиотек

api

То же, но видна и тем, кто подключает ваш модуль

Когда пишете библиотеку для других

compileOnly

Только при компиляции, в готовый JAR не попадает

Lombok, аннотации

runtimeOnly

Только при запуске, не нужна для компиляции

Драйвер базы данных

testImplementation

Только в тестах

JUnit, Mockito

gradle_03_deps.png

Разница между 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

Компиляция Java, базовые задачи сборки и тестов

application

Задачу run и сборку запускаемого дистрибутива

org.springframework.boot

Сборку исполняемого JAR со встроенным сервером, задачу bootRun

io.spring.dependency-management

Согласованные версии библиотек Spring — можно не писать версии вручную

checkstyle, jacoco

Проверку стиля кода и замер покрытия тестами

Связка плагинов 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_05_speed.png

Демон. 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.

Никита Вихров

2 дня назад

0

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845