JavaRush /Курсы /Java Server /Типы ошибок: compile, build, runtime

Типы ошибок: compile, build, runtime

Java Server
4 уровень , 4 лекция
Открыта

1. «Не работает» — не диагноз

К этому моменту у нас уже есть три честные контрольные точки: classes, jar и run. Одна показывает, компилируется ли код и ресурсы, другая — упаковывается ли артефакт, третья — стартует ли приложение. Теперь из этого можно сделать самую полезную привычку дня: сначала понять, на каком этапе всё сломалось.

Когда начинаешь собирать и запускать проект через Gradle, в какой-то момент случается неизбежное: что-то ломается. И первая реакция почти всегда одинаковая: «Gradle сломался», «Java сломалась», «я сломался». Спойлер: обычно ломается конкретное место в конкретный момент времени — просто мы ещё не привыкли отличать эти моменты.

В этой лекции мы научимся задавать себе правильный вопрос: на каком этапе сломалось? Это резко ускоряет починку. Потому что лечить «всё подряд» — это как чинить велосипед, начиная с покупки нового гаража: вроде деятельность бурная, а колёса всё равно не крутятся.

Три этапа проекта: компиляция → сборка → запуск

Если очень упростить, у нашего проекта есть три «состояния». Сначала он существует как исходники (Java-код + ресурсы). Потом превращается в собранный результат (классы, ресурсы, jar). А потом становится работающим процессом (запущенным приложением).

Здесь важно не запутаться в терминах: compile-time — это момент, когда Java-компилятор пытается превратить ваш .java в .class. build-time — более широкий этап, когда Gradle выполняет задачи сборки (включая компиляцию, обработку ресурсов и упаковку). runtime — это момент, когда приложение реально стартовало и ваш код уже выполняется.

Чтобы закрепить, давайте сведём это в таблицу. Она простая, но очень полезная (и да, в жизни такая табличка экономит часы).

Этап Что происходит по сути Чем обычно проверяем Где «чинить»
Compile-time Java-код компилируется в байткод (.class) ./gradlew classes В Java-коде: синтаксис, типы, сигнатуры
Build-time Gradle выполняет сборочные задачи: компиляция, ресурсы, упаковка, конфигурация application ./gradlew jar /
./gradlew build
В build.gradle.kts, в структуре проекта, в ресурсах
Runtime Запускается JVM-процесс, вызывается main(), выполняется ваш код ./gradlew run В логике приложения и в ресурсах/конфиге, доступных на запуске

И ещё одна полезная мысль: compile-time ошибки почти всегда проявляются как падение Gradle на задаче :compileJava. То есть технически это «ошибка во время сборки», но по смыслу — ошибка компиляции Java-кода. Поэтому мы и выделяем её в отдельный класс: чтобы мозг не говорил «опять Gradle», когда на самом деле компилятор честно сообщает, что мы забыли ;.

2. Compile-time: ошибки компиляции Java

Compile-time ошибки — самый «добрый» тип ошибок. Они неприятные, но честные: приложение даже не стартует, пока вы не приведёте код в порядок. Компилятор не умеет «догадываться» о вашем намерении, поэтому реагирует строго. И в этом есть плюс: вы узнаёте о проблеме сразу, а не через 15 минут, когда она выстрелит во время выполнения у пользователя (или у вас, но уже после кофе).

Обычно compile-time ошибки появляются из-за синтаксиса, несовпадения типов, неправильных сигнатур методов, отсутствующих импортов, неправильного использования static, неверных аргументов вызова. Важно: такие ошибки не зависят от того, запускаете вы из IDE или через Gradle — компилятор один и тот же, просто IDE часто подсвечивает проблему раньше.

Как выглядит compile-time ошибка

Возьмём самый банальный пример: забыли точку с запятой. Код небольшой, но он уже ломает весь этап компиляции.

package com.example.readlater;

public class ReadLaterApplication {
    public static void main(String[] args) {
        // Компилятор упадёт здесь: в конце инструкции нужна точка с запятой
        System.out.println("ReadLater Starter") // <- забыли ;
    }
}

Если вы запустите:

./gradlew classes

Gradle дойдёт до :compileJava и остановится. В сообщении будет что-то вроде `;` expected и указание строки. Самое ценное здесь — номер строки и конкретная причина, а не общий крик «BUILD FAILED».

Compile-time: «я же почти угадал тип»

Чуть более жизненный вариант: вы ожидаете число, а работаете со строкой (или наоборот). Компилятор в такие игры не играет.

package com.example.readlater;

public class ReadLaterApplication {
    public static void main(String[] args) {
        // "8080" в кавычках — это String, а не число
        int port = "8080"; // compile-time: incompatible types
        System.out.println(port);
    }
}

В голове это иногда выглядит как «ну 8080 же число, чего ты…», но для Java кавычки — это сразу String.

Быстрая локализация compile-time проблемы

Лайфхак новичка, который неожиданно превращается в профессиональную привычку: проверяйте компиляцию через classes, когда вам нужно понять, «код вообще компилируется или нет».

./gradlew clean classes

Почему это удобно? Потому что classes — достаточно «узкая» цель. Она компилирует Java и подготавливает ресурсы, но не начинает упаковывать jar и не запускает приложение. Если проблема именно в коде, вы увидите её быстро и без лишнего шума.

3. Build-time: ошибки сборки Gradle

Build-time ошибки — это всё, что ломается в процессе сборки проекта как артефакта, но не обязательно в Java-коде. Тут чаще всего виноваты настройки в build.gradle.kts, структура проекта, неправильная конфигурация плагинов, иногда ресурсы, иногда просто опечатки в DSL.

Это тот самый момент, когда человек пишет: «У меня ошибка, но в коде всё нормально!» — и часто оказывается прав. Потому что Gradle способен упасть ещё до компиляции или уже после неё, например на упаковке jar или на попытке подготовить run.

Kotlin DSL: Gradle «не понимает» скрипт

Представим, что вы в блоке application сделали опечатку в имени свойства:

plugins {
    // Подключаем поддержку запуска через `run` и настройку `application { ... }`
    application
}

application {
    // Намеренная ошибка в примере: свойства mainClasss не существует (опечатка)
    mainClasss = "com.example.readlater.ReadLaterApplication"
}

Это не Java-код, а build.gradle.kts. Если вы попробуете выполнить ./gradlew run или ./gradlew classes, Gradle может упасть ещё на этапе конфигурации проекта, то есть до выполнения задач. По смыслу это build-time-ошибка, потому что «сломана инструкция сборки».

Психологически это коварно: вы ничего не меняли в Java, а проект перестал собираться. Но если помнить, что build.gradle.kts — это тоже код (просто код для сборки), всё становится логично.

Плагин не подключен, а блок уже есть

Ещё один классический случай: вы пишете блок application { ... }, но забываете подключить application plugin (или случайно удалили его). Тогда Gradle не знает, что это за блок, и сборка падает.

plugins {
    // application  // <- кто-то закомментировал, и началось веселье
}

application {
    // Этот блок требует подключенного плагина `application`
    mainClass = "com.example.readlater.ReadLaterApplication"
}

Это тоже build-time проблема: структура и конфигурация сборки не соответствуют тому, что вы просите Gradle сделать.

После компиляции: ресурсы и упаковка

Иногда Java-код компилируется, но сборка всё равно падает. Например, если вы пытаетесь собрать jar, а в проекте странные проблемы с файлами или вы нечаянно руками правили то, что трогать не стоило.

На практике вы увидите это так: :compileJava прошла, а дальше упало на :processResources или :jar. Это важный сигнал: код компилируется, но сборочная система не смогла собрать артефакт.

Быстрая локализация build-time проблемы

Здесь работает тот же принцип «минимальной команды»: если вас интересует именно архив, используйте jar.

./gradlew jar

Если classes проходит, а jar падает, — вы уже сузили область поиска. И это гораздо приятнее, чем запускать build «на всякий случай» и получать тонну вывода.

4. Runtime ошибки: когда приложение уже выполняется

Runtime ошибки — это то, что случается после старта приложения, когда Java-код уже выполняется. Компилятор был доволен, Gradle собрал всё, run запустил JVM — и тут ваше приложение само говорит: «Ой». Обычно это исключение (exception) или неправильное поведение (например, печатает не то, что ожидали).

Для новичка это самый неприятный тип ошибок, потому что возникает ощущение: «Но ведь оно же собралось! Значит, должно работать!» Увы, сборка говорит только одно: «код формально корректен». А корректный код вполне может быть неправильным по смыслу. Java дисциплинированная, но ещё не телепат.

Простейший runtime пример: NullPointerException

Сделаем пример нарочно учебным: вы создали переменную null и полезли в неё. Код компилируется идеально. Но выполнение падает.

package com.example.readlater;

public class ReadLaterApplication {
    public static void main(String[] args) {
        // Переменная явно равна null
        String value = null;

        // Любой вызов метода/поля у null приведёт к NullPointerException
        System.out.println(value.length()); // runtime: NullPointerException
    }
}

Запуск:

./gradlew run

И вы увидите stack trace, где будет написано, на какой строке произошло падение. Это уже runtime, потому что JVM реально выполняла ваш код и дошла до проблемного места.

Runtime и ресурсы: getResourceAsStream возвращает null

Это особенно важно после лекции про src/main/resources. getResourceAsStream() выглядит дружелюбно, но у него есть характер: если ресурс не найден, он возвращает null, а не кидает исключение. И если вы сразу начинаете читать из null, получаете NPE.

Вот так можно «случайно» устроить себе runtime-падение, если файл banner.txt не лежит там, где вы думаете, или вы ошиблись в имени.

package com.example.readlater;

import java.io.IOException;
import java.io.InputStream;

public class ReadLaterApplication {
    public static void main(String[] args) throws IOException {
        // Если ресурс не найден, in будет null
        try (InputStream in = ReadLaterApplication.class.getResourceAsStream("/banner.txt")) {
            // Здесь упадём с NPE, если in == null
            System.out.println(in.available()); // runtime: NPE, если in == null
        }
    }
}

Чуть более взрослый, но всё ещё очень простой вариант — проверить null и упасть с понятным сообщением, чтобы вы не искали «почему NPE», а сразу увидели «ресурс не найден».

package com.example.readlater;

import java.io.InputStream;

public class ReadLaterApplication {
    public static void main(String[] args) {
        // Пытаемся найти ресурс в classpath
        InputStream in = ReadLaterApplication.class.getResourceAsStream("/banner.txt");

        // Явно проверяем "ресурс не найден"
        if (in == null) {
            throw new IllegalStateException("Resource /banner.txt not found on classpath");
        }

        System.out.println("Banner found!"); // Banner found!
    }
}

Обратите внимание: это всё ещё runtime-ошибка (если ресурса нет), но теперь она объясняет проблему человеческим языком.

5. Диагностика через classes, jar, run

Когда у вас есть три типа ошибок, возникает естественный вопрос: «Окей, а как быстро понять, какой именно тип у меня сейчас?» Тут помогает не магия, а маленькая дисциплина: проверять проект узкими командами и смотреть, на какой задаче всё упало.

В этой лекции мы специально опираемся на задачи, которые вы уже знаете: classes, jar, run. Это три контрольные точки: «код компилируется», «артефакт собирается», «приложение запускается».

Нарисуем простую блок-схему, которую можно держать в голове (или даже рядом с проектом, пока не привыкнете):

flowchart TD
    A["Запустили ./gradlew run и всё сломалось"] --> B{"Падает уже на :compileJava?"}
    B -->|Да| C["Compile-time: ошибка в Java-коде"]
    B -->|Нет| D{"Падает на конфигурации Gradle / build.gradle.kts?"}
    D -->|Да| E["Build-time: ошибка сборочного скрипта/настройки"]
    D -->|Нет| F{"classes проходит, но jar падает?"}
    F -->|Да| G["Build-time: проблема упаковки/ресурсов/структуры"]
    F -->|Нет| H["Runtime: приложение запустилось и упало в вашем коде"]

Если не хочется рисовать схемы, можно проговорить это обычной фразой: сначала я убеждаюсь, что компиляция проходит (classes), потом — что упаковка проходит (jar), и только после этого проверяю запуск (run). В какой точке сломалось, там и копаю.

6. Три мини-кейса на ReadLater Starter

Сейчас будет самое полезное: не просто теория про ошибки, а три типичные поломки, которые реально случаются у новичков, и то, как их быстро отличить. Я буду описывать это как мини-истории, потому что так мозгу проще запомнить.

Кейс A: проект не компилируется после маленькой правки

Вы добавили в ReadLaterApplication одну строку, и всё упало. Запускаете ./gradlew run и видите, что задача :compileJava FAILED. Это почти всегда compile-time. Вы не ищете проблему в ресурсах, не трогаете build/, не переустанавливаете Gradle (пожалуйста, не переустанавливайте Gradle). Вы открываете сообщение компилятора и исправляете код.

Самая частая причина — банальная синтаксическая ошибка или несовместимые типы. Исправили — и всё поехало.

Кейс B: Java-код не меняли, а сборка вдруг «упала сразу»

Вы ничего не меняли в Java, зато решили «чуть подправить build.gradle.kts». После этого любой запуск падает, иногда даже ./gradlew tasks ведёт себя странно. Это build-time, причём в чистом виде: сломан сборочный скрипт.

Здесь помогает простая привычка: если вы меняли build.gradle.kts и после этого всё сломалось, не ищите проблему в Java-коде. Возвращайте предыдущую правку, проверяйте, где опечатка, и помните, что Kotlin DSL — строгий DSL, он не любит «примерно правильные» имена.

Кейс C: run запускается, но приложение падает с исключением

Это runtime. И здесь важно научиться делать две вещи: во-первых, читать stack trace сверху вниз ровно до первой строки вашего кода, а во-вторых, не путать причину и место падения.

Например, если вы видите NullPointerException в строке, где читаете ресурс, причина почти всегда не «Java сломалась», а «ресурс не найден / переменная null». И это уже можно чинить в своём коде: проверкой null, корректным путём к ресурсу, правильным расположением файла.

7. Чтение вывода Gradle

Вывод Gradle поначалу выглядит как «консольный шум в стиле научной фантастики». Но он вполне читаемый, если смотреть в правильное место. Важна простая привычка: Gradle почти всегда говорит, какая задача упала. А задача — это подсказка, на каком этапе произошла проблема.

Если вы видите что-то вроде Task :compileJava FAILED, это одна область поиска. Если Task :processResources FAILED — другая. Если ошибка произошла ещё до списка задач, это часто значит, что Gradle не смог даже сконфигурировать проект, то есть проблема в build.gradle.kts.

Иногда полезно запускать Gradle так, чтобы он показал больше контекста. Не как «магическое заклинание», а просто как способ получить детали, если стандартного текста не хватает.

./gradlew run --stacktrace

Это не делает вас «продвинутым билд-инженером». Это просто просьба: «покажи подробнее, где упало». Но важно не тонуть в деталях. Сначала ищите: какая задача, какая причина, какой файл и строка. А уже потом, если нужно, смотрите глубже.

И ещё один совет: никогда не чините проблему в папке build/. Если что-то сломано, вы исправляете исходники (src/main/java, src/main/resources) или build-скрипт (build.gradle.kts). Папка build/ — это как временная посуда после готовки: её можно помыть (clean), но бессмысленно в неё «добавлять ингредиенты».

8. Типичные ошибки при диагностике

Ошибка №1: называть любую проблему «ошибкой Gradle».
Очень легко свалить всё на Gradle, потому что сообщение об ошибке выводит именно он. Но Gradle часто просто честно показывает, что упал компилятор Java или ваша программа в runtime. Если вы увидели :compileJava FAILED, чинить нужно Java-код, а не «градл».

Ошибка №2: пытаться лечить runtime-проблему через clean.
clean — полезная задача, но она не лечит NullPointerException и не добавляет файл banner.txt в правильную папку. Иногда clean помогает убрать мусор после кривых экспериментов, но чаще это просто лишнее действие. Если приложение падает в main(), чистка build/ не превращает null в строку.

Ошибка №3: запускать всегда build, даже когда нужна узкая проверка.
Когда вы в панике запускаете «самую большую команду» (build), то получаете и самый большой вывод, а причину видеть становится сложнее. Очень часто достаточно classes, чтобы понять, что код не компилируется, или jar, чтобы увидеть, что ломается упаковка, или run, чтобы поймать runtime-исключение.

Ошибка №4: чинить ресурсы, редактируя build/resources/main.
Это один из самых обидных сценариев: вы нашли файл в build/resources/main, поправили его, всё заработало… а потом снова сломалось после следующей сборки. Потому что build/ перегенерируется. Источник истины — src/main/resources. Всё остальное — временный результат.

Ошибка №5: путать «не нашёл main class» с «не компилируется».
Если Gradle не может запустить mainClass, это может выглядеть страшно, но не всегда означает, что Java-код сломан. Код может прекрасно компилироваться, просто mainClass в build.gradle.kts указан неверно: не тот пакет или не то имя. Это часто воспринимается как runtime, но по смыслу остаётся конфигурационной проблемой сборки.

1
Задача
Java Server, 4 уровень, 4 лекция
Недоступна
Разделить compile-time и runtime ошибки на одном классе
Разделить compile-time и runtime ошибки на одном классе
1
Задача
Java Server, 4 уровень, 4 лекция
Недоступна
Разделить build-time и runtime ошибки на разных причинах
Разделить build-time и runtime ошибки на разных причинах
1
Опрос
Задачи Gradle, 4 уровень, 4 лекция
Недоступен
Задачи Gradle
Сборка, ресурсы и запуск
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ