1. Gradle и точка входа main()
К этому моменту у нас уже есть Java-код, ресурсы и рабочий ReadLaterApplication, который читает banner.txt из classpath. Но для Gradle это пока просто Java-проект. Чтобы задача run вообще появилась и стабильно запускала один и тот же main(), проект нужно явно объявить приложением и назвать точку входа.
В Java точка входа — это метод public static void main(String[] args). Но в проекте может быть несколько классов с main() (особенно в учебных экспериментах), и Gradle не обязан угадывать, какой из них «главный». Поэтому мы делаем выбор явным: выбираем один класс-вход и фиксируем его в build.gradle.kts.
2. Роли плагинов java и application
До этого момента у нас уже был плагин java, и он отвечает за компиляцию, структуру исходников и типовые задачи вроде compileJava и classes. Но плагин java сам по себе не говорит Gradle: «Эй, это приложение, его нужно уметь запускать как программу». Он скорее про «собрать код».
Плагин application как раз добавляет проекту «поведение приложения»: он приносит задачу run и связанную с ней конфигурацию. То есть он не меняет вашу Java-программу по волшебству и не добавляет в неё main() — main() всё равно пишете вы. Он просто даёт Gradle возможность корректно запускать программу через JVM: собрать правильный classpath на запуске (включая зависимости) и вызвать нужный main().
Чтобы уложить это в одну картинку, удобно держать в голове такую мини-таблицу:
| Вопрос | java plugin | application plugin |
|---|---|---|
| «Где лежит код и ресурсы?» | Да, задаёт стандартную структуру src/main/java и src/main/resources | Не про это |
| «Как компилировать?» | Да (compileJava, classes) | Косвенно использует результат компиляции |
| «Как запустить как программу?» | Нет (IDE сможет, Gradle — не обязан) | Да (run) |
| «Как понять, какой main() запускать?» | Не решает | Решает через mainClass |
Можно представить это как роли в оркестре. java plugin — это человек, который готовит сцену: ставит пюпитры, печатает ноты, раздаёт инструменты. application plugin — дирижёр, который говорит: «Сейчас играем вот эту пьесу и начинаем с этой партии». А mainClass — та самая «первая скрипка»: конкретная точка, с которой всё начинается.
И ещё один важный момент, который новички часто путают: application plugin не имеет отношения к файлу application.properties (это совсем другая история). Здесь «application» означает «запускаемая программа».
3. mainClass и полное имя класса
mainClass — это настройка для Gradle, которая указывает, какой класс нужно запускать в задаче run. И вот здесь начинается типичная путаница: в mainClass пишется не «имя файла» и не «путь в файловой системе», а полное имя Java-класса, то есть имя вместе с пакетом.
Например, если у вас файл лежит так:
src/main/java/com/example/readlater
└─ ReadLaterApplication.java
и внутри него в первой строке написано:
package com.example.readlater;
то полное имя класса будет:
com.example.readlater.ReadLaterApplication
Это не косметика, а строгое правило. Java-пакеты — это «адреса» в пространстве имён. Короткое имя ReadLaterApplication похоже на имя «Саша» в офисе на 200 человек: технически имя есть, но попробуй пойми, какой именно Саша нужен. Полное имя com.example.readlater.ReadLaterApplication — это уже «Саша Петров с курса по бэкенду, стол справа, рядом с кофемашиной». Шансы ошибиться резко падают.
Важно также понимать разницу между тремя похожими, но разными вещами:
| Что это | Пример | Где используется |
|---|---|---|
| Короткое имя класса | ReadLaterApplication | В коде, если класс в том же пакете или импортирован |
| Полное имя класса (FQCN) | com.example.readlater.ReadLaterApplication | В настройке mainClass, в логах JVM, в ошибках ClassNotFound |
| Путь к файлу | src/main/java/com/example/readlater/ReadLaterApplication.java | В файловой системе проекта, в IDE |
Если перепутать и написать в mainClass путь к файлу, Gradle не станет «угадывать, что вы имели в виду». Он честно попытается запустить класс с таким именем и упадёт. И это как раз тот случай, когда ошибка выглядит пугающе, но лечится одной буквой или одной точкой в пакете.
4. ReadLaterApplication: точка входа
Хорошая привычка для проекта — договориться об одном главном классе входа с предсказуемым названием. В проекте ReadLater Starter мы фиксируем класс ReadLaterApplication. Это не «единственно правильное имя во вселенной», но это понятная договорённость: если кто-то открывает проект впервые, он быстро находит, где старт.
Рабочая версия этого класса у нас уже есть: она читает banner.txt из classpath. Сейчас важно не переписывать main() заново, а заметить две вещи: пакет класса и корректную сигнатуру точки входа.
package com.example.readlater;
public class ReadLaterApplication {
public static void main(String[] args) {
// здесь остаётся рабочая логика приложения
}
}
Обратите внимание на три вещи, которые часто проскальзывают мимо глаз. Во-первых, main должен быть static, иначе JVM не сможет вызвать его без экземпляра объекта. Во-вторых, сигнатура должна быть именно main(String[] args) (пара вариаций допустима, но сейчас нам это не нужно). Даже если аргументы вам пока не нужны, сигнатура остаётся именно такой: JVM смотрит на форму точки входа, а не на богатство внутренней логики. В-третьих, пакет должен соответствовать директории, иначе IDE может «починить» запуск, а Gradle — нет.
5. build.gradle.kts: application и mainClass
Теперь осталось связать этот класс с конфигурацией Gradle. После того как у нас есть ReadLaterApplication, Gradle нужно объяснить, что именно он является точкой входа. Это делается в build.gradle.kts через подключение плагина application и блок application { ... }.
В Kotlin DSL это выглядит максимально прямолинейно. Минимальный фрагмент, который вам нужен, такой:
plugins {
java // Компиляция, стандартная структура проекта, задачи вроде compileJava/classes
application // Добавляет задачу run и конфигурацию запуска приложения
}
application {
// Полное имя класса (FQCN): пакет + имя класса
mainClass = "com.example.readlater.ReadLaterApplication"
}
Здесь происходят сразу две важные вещи. Подключение application добавляет задачу run и делает проект «запускаемым» на уровне Gradle. А mainClass фиксирует, какой класс нужно запускать. Строка "com.example.readlater.ReadLaterApplication" — это то самое полное имя класса, о котором мы говорили: пакет + имя класса через точку.
Если вы забыли указать mainClass, Gradle не сможет корректно выполнить run и будет ругаться настройочной ошибкой. Если указали mainClass, но ошиблись в пакете или названии класса, сборка может пройти, а запуск упадёт уже при попытке старта JVM с сообщением уровня «не найден основной класс». И это нормально: компиляция и запуск — разные этапы жизни проекта.
6. Задача run и запуск main()
После подключения application и указания mainClass задача run получает конкретную цель: поднять com.example.readlater.ReadLaterApplication с корректным classpath на запуске.
Самый честный способ проверить, что всё настроено правильно, — запустить приложение через Gradle:
./gradlew run # Запуск mainClass через Gradle (с правильным runtime classpath)
Если всё настроено правильно, вы увидите тот вывод, который делает текущий ReadLaterApplication — сейчас это баннер из banner.txt.
Также полезно один раз увидеть, что run действительно связан с подготовкой классов и ресурсов. Gradle обычно покажет что-то вроде:
> Task :compileJava
> Task :processResources
> Task :classes
> Task :run
Это хорошее напоминание: application plugin не компилирует и не создаёт main() за вас. Он просто связывает уже существующий класс с задачей запуска.
Если нужно механически проверить передачу аргументов, application plugin умеет и это:
./gradlew run --args="hello" # Аргументы попадут в String[] args вашего main()
Даже если текущая версия ReadLaterApplication их пока не печатает, механизм уже тот же самый: Gradle передаст значения в String[] args.
И давайте честно: полезно один раз увидеть типовую поломку. Например, если написать:
application {
// Намеренная опечатка: JVM попытается найти класс по этому имени и не найдёт
mainClass = "com.example.readlater.ReadLaterApplicatoin"
}
то ./gradlew run упадёт, потому что JVM не найдёт класс. Здесь код может прекрасно компилироваться, но запуск всё равно сломается: вы дали Gradle адрес, по которому никто не живёт.
7. Типичные ошибки при работе с application и mainClass
В этой теме есть особый вид ошибок: они почти всегда выглядят страшнее, чем есть на самом деле. Это как прийти в банк и увидеть очередь на 40 человек: кажется, что вы пропали, а потом выясняется, что половина людей просто спрашивает пароль от Wi‑Fi. Ниже — самые частые грабли, на которые наступают новички, и как их распознать по симптомам.
Ошибка №1: указать в mainClass короткое имя класса без пакета.
Если написать mainClass = "ReadLaterApplication", Gradle, скорее всего, не найдёт класс, потому что для JVM это не полное имя. Иногда кажется, что «ну IDE же видит», но IDE здесь не показатель. Правильная стратегия простая: открыть ReadLaterApplication.java, посмотреть строку package ...; и склеить package + "." + имяКласса.
Ошибка №2: перепутать имя класса и путь к файлу.
Новички иногда пытаются написать что-то вроде mainClass = "src/main/java/com/example/readlater/ReadLaterApplication.java". Это логично по-человечески («вот же файл!»), но для JVM бессмысленно: запускается класс, а не файл. В mainClass всегда точки, а не слеши, и никогда не пишется .java.
Ошибка №3: забыть подключить application plugin и пытаться запускать run.
Если в plugins { ... } нет application, задача run просто не появится. Симптом обычно такой: Gradle говорит, что задачи run не существует, или предлагает что-то похожее, но не то. Лечение скучное, зато эффективное: добавить application в plugins.
Ошибка №4: класс есть, mainClass указан, но «Main method not found».
Это классика, когда main() написан неправильно. Например, забыли static, написали main(String args) вместо String[] или случайно сделали метод private. JVM в этом случае честно говорит: класс нашла, но точку входа — нет. Лечится проверкой сигнатуры: public static void main(String[] args).
Ошибка №5: пакет в коде не соответствует директории, и IDE «поддерживает иллюзию», а Gradle ломается.
Например, файл лежит в src/main/java/com/example/readlater/ReadLaterApplication.java, но внутри написано package com.example;. IDE может запустить, потому что у неё свои внутренние механизмы и кеши, а Gradle будет собирать по правилам. Это неприятно, потому что выглядит как «магия». Решение — дисциплина: директория и package должны совпадать, а mainClass — совпадать с обоими.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ