1. Різні режими запуску
Коли застосунок «запускається», у початківця часто спрацьовує внутрішня позначка: «Ну все, працює». Проблема в тому, що способів запуску насправді кілька, і кожен перевіряє своє. Запуск з IDE — це затишний плед, bootRun — уже чесніша перевірка, а java -jar — холодний душ: він не зважає на ваші налаштування в IntelliJ і не підлаштовується під очікування.
У реальному житті Spring Boot‑сервіс майже ніколи не стартує так, як ви запускаєте його в перші тижні навчання. На сервері немає кнопки Run, а в колеги на ноутбуці — ваших «Run Configuration», де ви акуратно виставили профілі, порт і змінні середовища. Тому сьогодні нам потрібно розкласти все по поличках: що саме ми запускаємо і які перевірки проходить застосунок у кожному режимі.
Якщо говорити чесно, одна з найтиповіших професійних ситуацій звучить так: «У мене в IDE працює, а в колеги впало». І майже завжди це не «магія Spring», а просто різні режими запуску та різні припущення, сховані в проєкті.
Терміни та форми запуску
Коли ми говоримо «запуск застосунку», ми часто не уточнюємо, що саме запускається: набір .class-файлів у каталозі чи один зібраний .jar. Саме через цю нечіткість і виникають помилки рівня «я вніс зміни в код, а java -jar поводиться по-старому». Давайте домовимося про терміни, щоб далі говорити не жестами, а словами.
Режим запуску — це поєднання двох речей: як ви запускаєте процес і звідки процес бере класи/ресурси/залежності.
Артефакт збирання — це підсумковий результат процесу збирання. Для нас у цьому модулі (і взагалі в курсі) головним артефактом буде .jar, який лежить у build/libs.
Упакований застосунок (packaged application) — це застосунок у вигляді готового файла-артефакта, який можна винести з проєкту і запустити без IDE.
Розгорнута форма (exploded form) — це запуск застосунку не з одного jar, а з «розсипу» каталогів: окремо скомпільовані класи, окремо ресурси, окремо залежності на classpath.
Є ще два терміни, які спливуть уже сьогодні, навіть якщо ми не будемо їх розбирати скальпелем.
Launcher — це вбудований механізм у Boot-jar, який допомагає запускати застосунок як «один файл». Деталей сьогодні не розбираємо; нам достатньо розуміти, що Boot-jar — це не «звичайний jar із Java Core», а jar, розрахований на запуск.
Поточний каталог процесу — це папка, відносно якої розв’язуються відносні шляхи. У Java його можна побачити через System.getProperty("user.dir"). І так, він раптово стає важливим, коли ви запускаєте jar не з кореня проєкту.
2. Три режими: IDE, bootRun, java -jar
Один і той самий catalog-service справді можна запустити трьома способами — і це не три застосунки, а три різні життєві ситуації. Якщо провести аналогію, то IDE-запуск — це коли ви готуєте вдома на своїй кухні, bootRun — коли дотримуєтеся рецепта з книжки й перевіряєте, що інгредієнти справді на місці, а java -jar — коли ви віддаєте страву в доставку і перевіряєте, чи доїде вона цілою.
Нижче — компактна таблиця, яка фіксує головну ідею: режими відрізняються не логікою catalog-service, а тим, як побудовано classpath, де лежать ресурси і хто підмішав вам параметри запуску.
| Режим | Чим запускаємо | Звідки беруться класи та ресурси | Головна перевага | Що НЕ перевіряє |
|---|---|---|---|---|
| IDE Run | IDE (Run/Debug) | зазвичай build/classes + build/resources, але з особливостями IDE | супершвидко і зручно для розробки | готовність артефакта і запуск як одного файла |
| bootRun | Gradle task | exploded form: каталоги збирання + залежності на classpath | відтворювано і ближче до реального збирання | упакування застосунку в jar |
| java -jar | JVM напряму | packaged application: один jar-артефакт | максимально наближено до реальної експлуатації | комфорт IDE та її підказки |
Щоб картина стала зовсім чіткою, корисно побачити просту схему:
flowchart TD
A["Вихідні файли: src/main/java та src/main/resources"] --> B["Компіляція та збирання класів/ресурсів"]
B --> C["Exploded form: build/classes + build/resources"]
C --> D1["Запуск з IDE"]
C --> D2["./gradlew bootRun"]
C --> E["Збирання jar-артефакта"]
E --> F["java -jar build/libs/...jar"]
Зверніть увагу: IDE Run і bootRun — це про запуск з каталогів, а java -jar — це запуск готового файла. І саме тому java -jar іноді стає тим самим «неприємним, але чесним другом», який першим помічає ваші приховані припущення.
Запуск з IDE
Запуск з IDE (IntelliJ IDEA, Eclipse тощо) — це те, з чого майже всі починають, і це нормально. IDE уміє швидко перебудовувати проєкт, зручно підключати відлагоджувач, показувати логи, підсвічувати помилки конфігурації ще до запуску. Але в IDE є побічний ефект: вона часто робить за вас трохи більше, ніж ви усвідомлюєте, і ви починаєте думати, що так буде всюди.
Найважливіший ризик IDE-запуску — приховані налаштування, які живуть не в репозиторії, а у вас у голові або в IDE. Наприклад, ви могли один раз поставити SPRING_PROFILES_ACTIVE=local у Run Configuration, і тепер завжди запускаєте з локальним профілем, навіть не помічаючи цього. Або IDE виставила робочий каталог у корінь проєкту, і відносні шляхи раптово «працюють». А потім ви запускаєте jar з іншої папки — і починається.
Точка входу при цьому залишається звичайною і передбачуваною — і це добре: у нас як і раніше є main(), і в ньому як і раніше SpringApplication.run(...).
package com.example.catalogservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // Головна анотація Spring Boot: вмикає автоконфігурацію та сканування компонентів
public class CatalogServiceApplication {
public static void main(String[] args) {
// Єдина «офіційна» точка входу в усіх режимах запуску (IDE / bootRun / java -jar)
SpringApplication.run(CatalogServiceApplication.class, args);
}
}
Якщо ви бачите тут «магічний рядок, який запускає всесвіт», це нормальна стадія. Але важлива думка дня: магія не в main(). Сюрпризи зазвичай ховаються в середовищі запуску: хто сформував classpath, які args прилетіли, який профіль активний, звідки читаються ресурси.
bootRun
bootRun — це запуск застосунку через Gradle, тобто через той самий інженерний механізм, який керує залежностями, компіляцією та завданнями збирання. Він і далі запускає застосунок у розгорнутій формі (не з jar), але робить це більш відтворювано: у вас з’являється відчуття, що якщо команда в терміналі працює, то з високою ймовірністю спрацює і в колеги.
Типова команда виглядає так:
# Запуск застосунку через Gradle (у розгорнутій формі)
./gradlew bootRun
Якщо ви на Windows, то зазвичай це буде:
# Те саме, але через bat-обгортку для Windows
gradlew.bat bootRun
Що важливо розуміти на рівні здорового глузду: bootRun запускає те, що Gradle вважає вашим застосунком. Якщо у вас залежність не підтягнулася, якщо зламався кеш Gradle, якщо ви забули синхронізувати проєкт — bootRun частіше покаже проблему раніше, ніж IDE-запуск (бо IDE іноді «живе своїм життям» і може тримати старий стан проєкту).
А ще bootRun корисний тим, що він не прив’язаний до кнопки й інтерфейсу IDE. Це означає, що ви можете швидко виробити звичку: «Будь-яка фіча має стартувати не тільки в IDE, а й із термінала». Ця звичка не робить вас «олдскульним консольним магом» — вона просто зменшує кількість сюрпризів, коли проєкт переїжджає з вашої машини.
Якщо потрібно передати аргументи застосунку, Gradle робить це через --args. Це частий момент, де новачки починають сперечатися з лапками, а лапки перемагають.
# Важливо: це аргументи застосунку, а не аргументи Gradle
./gradlew bootRun --args="--server.port=8085"
Тут сенс простий: --args="..." — це аргументи вашого застосунку, а не Gradle. І так, якщо у вас в аргументах кілька параметрів, усі вони мають потрапити всередину цих лапок, інакше Gradle почне думати, що ви намагаєтеся керувати ним самим.
java -jar
Запуск через java -jar — це момент істини. Ви більше не запускаєте «проєкт», ви запускаєте файл. І цей файл має містити все, що потрібно для старту: класи застосунку, ресурси та спосіб коректно підняти середовище виконання Spring Boot. На цьому кроці IDE уже не може «допомогти» — і в цьому якраз цінність.
Команда виглядає максимально по-шкільному:
# Запуск уже зібраного артефакта: тут важливий конкретний jar-файл у build/libs
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar
Якщо ви відчуваєте легку тривогу через назву файла — це нормально. У jar-файла часто є версія, суфікси та інші радощі життя. У реальній роботі перша дія — не вгадувати, а подивитися, що лежить у build/libs. Важливо запам’ятати лише принцип: java -jar запускає те, що ви зібрали, а не те, що у вас зараз відкрите в IDE.
Психологічно корисно сприймати java -jar як «передачу застосунку іншій людині». Ви ніби кажете: «Ось тобі один файл, запусти його, будь ласка». Якщо для запуску потрібно щось іще — наприклад, відкрити IntelliJ, виставити робочий каталог або мати саме такий профіль — значить, процес поки що не відтворюваний.
І так, саме тут спливають речі на кшталт «я читав файл із src/main/resources/...». Бо поза проєктом каталогу src/ просто не існує. У jar є свій внутрішній світ, і він не зобов’язаний повторювати вашу структуру репозиторію.
3. Аргументи та перевірки запуску
Одна точка входу: args доходять усюди
Дуже важливий заспокійливий факт: у всіх трьох режимах запуску точка входу залишається тією самою. Не існує окремого «Spring Boot режиму», де запускається інший main(). Запуск — це завжди виклик CatalogServiceApplication.main(args), а далі Boot розгортає контекст і піднімає вбудований сервер.
Щоб відчути це руками, зручно зробити мікроскопічний лог на старті: вивести, які аргументи реально побачив застосунок, і з якого каталогу він стартував. Це корисно саме сьогодні, тому що args і user.dir — два параметри, які часто «випадково різні» між IDE та java -jar.
Нижче — приклад невеликого ApplicationRunner. Його логіка не має перетворюватися на бізнес-фічу; це радше «пломба», яка показує: аргументи справді доходять.
package com.example.catalogservice.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // Конфігураційний клас: тут оголошуємо інфраструктурні біни
public class StartupArgsConfiguration {
private static final Logger log = LoggerFactory.getLogger(StartupArgsConfiguration.class);
@Bean
ApplicationRunner startupArgsReporter() {
// ApplicationRunner виконується після підняття контексту і перед тим, як застосунок «стане на ноги»
return args -> {
// Показуємо, які опції реально побачив Spring Boot (корисно порівнювати IDE vs java -jar)
log.info("Назви опцій: {}", args.getOptionNames()); // наприклад: [server.port]
// Поточний каталог процесу впливає на відносні шляхи
log.info("user.dir: {}", System.getProperty("user.dir")); // наприклад: /Users/alex/projects/catalog-service
};
}
}
Зверніть увагу на дві речі. По-перше, ми записуємо це в лог через Logger, бо ми вже дорослі й проходили logging (так, це тонкий натяк на дисципліну). По-друге, user.dir виглядає настільки нудно, що його легко недооцінити — доки ви не запустите jar з іншої папки і не зрозумієте, чому відносний шлях раптово «шукається не там».
Три запуски як три перевірки
Розділяти режими запуску корисно не заради теорії, а заради простої інженерної поведінки: ви починаєте розуміти, який запуск що перевіряє, і перестаєте вимагати від одного режиму того, що він не зобов’язаний перевіряти. IDE-запуск чудовий для розробки та відлагодження, bootRun зручний для відтворюваної перевірки «проєкт живий як Gradle-проєкт», а java -jar перевіряє «застосунок живе як артефакт».
Є зручний спосіб тримати це в голові: не намагайтеся вибрати «єдино правильний режим», а використовуйте режими за призначенням. Коли ви пишете код і хочете швидко побачити результат — IDE ідеальна. Коли вам важливо переконатися, що проєкт чесно збирається і запускається через інструмент збирання — bootRun робить це без зайвих ритуалів. Коли ви хочете переконатися, що застосунок можна винести з репозиторію і запустити як сервіс — java -jar перетворює це на перевірюваний факт, а не на оптимістичну надію.
Якщо сприймати ці три запуски як різні «рівні перевірки», зникає відчуття, що jar-запуск «вередує». Він просто відповідає на інше питання. IDE запитує: «Чи можна швидко попрацювати?», а jar запитує: «Чи можна жити без твоєї IDE?».
4. Типові помилки під час роботи з трьома режимами запуску
Майже всі помилки в цій темі виглядають однаково: людина запускає застосунок одним способом, а потім робить висновки про інший. Це як перевіряти, що човен не тоне, сидячи у ванні. Ніби й схоже на воду, але деталі трохи відрізняються — і саме деталі потім кусаються.
Помилка №1: «Якщо працює в IDE — значить готово».
IDE справді вміє запускати Spring Boot сервіс, і на етапі навчання це надзвичайно важливо. Але готовність до запуску як артефакт перевіряється лише тоді, коли ви запускаєте артефакт. Інакше ви несвідомо доводите лише те, що у вас правильно налаштована IDE.
Помилка №2: плутати bootRun і java -jar.
bootRun — це запуск застосунку з проєкту в розгорнутому вигляді. java -jar — запуск одного зібраного файла. У них різні джерела класів і ресурсів, а отже, різні класи проблем. Якщо сприймати їх як одне й те саме, ви будете «лікувати симптоми», а не розуміти причини.
Помилка №3: зберігати важливі параметри запуску лише в IDE Run Configuration.
Сюди потрапляють профілі, порти, змінні середовища і будь-які override-значення. Поки ви самі, здається, що це зручно. Щойно ви передаєте проєкт іншому розробникові або намагаєтеся запустити jar — це перетворюється на «а в мене працює». Правильний напрямок мислення: параметри мають передаватися через аргументи командного рядка / змінні середовища / зовнішні конфіги, а IDE нехай просто повторює цей сценарій.
Помилка №4: запускати «не той jar» і порівнювати його з «новим кодом».
Це класика жанру: ви змінили код, забули пересобрати, запустили старий jar і здивувалися, що нічого не змінилося. java -jar запускає файл, а не ваші думки про те, що ви точно все зберегли. Якщо поведінка не збігається з очікуванням, одне з перших запитань має бути максимально нудним: «А я точно пересобрав артефакт?»
Помилка №5: вважати, що відносні шляхи завжди однакові.
В IDE робочий каталог часто виставлений у корінь проєкту автоматично, і ви звикаєте, що відносні шляхи «просто працюють». Але jar можна запустити з будь-якої папки, і тоді відносні шляхи починають означати зовсім інше. Навіть якщо ви ще не зіткнулися з цим особисто, краще завчасно пам’ятати: поточний каталог процесу — частина середовища запуску, а не властивість вашого коду.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ