1. Коли JSON зручніший за змінні середовища
Уявіть, що ви запускаєте catalog-service не «як завжди», а в невеликому демонстраційному режимі. Хочеться швидко змінити заголовок каталогу, увімкнути технічний режим (maintenance), зменшити ліміт обраних курсів і, наприклад, змінити порт, щоб не конфліктувати з іншим сервісом на вашому ноутбуці. Технічно це все — звичайні властивості. Практично — вводити їх вручну по одній стає стомливо.
Три канали запуску вже зрозумілі: змінні середовища, -D та --key=value. Але щойно потрібно передати не одну властивість, а цілу групу пов’язаних значень, робити це по одній уже незручно. Саме тут і з’являється JSON-блок: не нова модель конфігурації, а компактніше перевизначення поверх уже знайомої мапи.
Щоб відчути біль, достатньо подивитися на такий запуск. Він робочий, але якщо ви помилилися в одному символі, застосунок просто спокійно проігнорує неправильний ключ і зробить вигляд, що так і було задумано (а ви потім дві години сперечаєтеся з реальністю).
# Довгий запуск: багато окремих змінних середовища
APP_CATALOG_TITLE="Demo Catalog" \
APP_CATALOG_MAINTENANCE_MODE=true \
APP_CATALOG_MAX_FEATURED_COUNT=1 \
SERVER_PORT=9095 \
./gradlew bootRun
І ось тут виникає проста інженерна думка: «А чи можна передати це одним блоком, щоб усе було атомарно і в одному місці?» Spring Boot відповідає: «Можна. Тримайте SPRING_APPLICATION_JSON».
2. Що таке SPRING_APPLICATION_JSON
Важливо відразу правильно зрозуміти ідею: SPRING_APPLICATION_JSON — це не «JSON-конфіг замість YAML». Це спосіб передати кілька властивостей одним рядком, найчастіше через змінну середовища. Spring Boot уміє взяти цей рядок, розібрати JSON і додати отримані значення до Environment як окреме джерело властивостей.
На практиці в механізму є два «входи», які корисно знати за назвами, щоб не плутатися в документації та в чужих прикладах:
- SPRING_APPLICATION_JSON — ім’я змінної середовища (environment variable), куди ви записуєте JSON.
- spring.application.json — те саме за змістом, але як звичайна назва властивості. Її можна передати і як -Dspring.application.json=..., і як --spring.application.json=....
Суть у тому, що Boot не просто читає цей рядок. Він перетворює його на окреме джерело властивостей — умовне «JSON-джерело» — і піднімає його досить високо в ланцюжку пріоритетів.
Щоб було видно, куди JSON-джерело вбудовується в уже знайому драбину, тримайте просту схему пріоритетів, зручну саме для цієї теми:
flowchart TD
A["аргументи командного рядка --key=value"] --> B["SPRING_APPLICATION_JSON / spring.application.json"]
B --> C["системні властивості Java -Dkey=value"]
C --> D["змінні середовища ОС"]
D --> E["зовнішні файли конфігурації"]
E --> F["вбудований application.yaml"]
Ця діаграма не про «всі можливі джерела на світі», а про практичну карту: де значення за замовчуванням, де перевизначення і чому одне значення перемагає інше.
3. Розплющення JSON у ключі
З точки зору людини JSON — це дерево (вкладені об’єкти). З точки зору Environment — це плоскі ключі на кшталт app.catalog.title. Тому Boot робить зрозумілу річ: бере вкладеність і розплющує її в ключі, розділені крапками.
Наприклад, ось такий JSON:
{
"app": {
"catalog": {
"title": "JSON title",
"maintenance-mode": true
}
}
}
перетвориться на два звичайні записи:
- app.catalog.title = JSON title
- app.catalog.maintenance-mode = true
Можна дивитися на це як на YAML, тільки без красивих відступів і без коментарів. Так, це мінус. JSON не терпить коментарів. Він суворий, як викладач на заліку: «зайва кома — і до побачення».
Хороший спосіб закріпити розуміння — невелика табличка відповідностей:
| JSON-фрагмент | У що перетворюється в Environment |
|---|---|
|
server.port=9095 |
|
app.catalog.title=Demo |
|
app.catalog.maintenance-mode=true |
Окремо зверніть увагу на типи. У JSON true — це булеве значення, а "true" — це рядок. Для Environment у більшості випадків це все одно виглядатиме як рядок, але під час спроби отримати значення з приведенням типів різниця може стати помітною. І, що важливіше, різниця помітна вам як людині: "true" виглядає як «я не певен, що роблю, але хай буде в лапках про всяк випадок». Лапки в конфігурації рідко додають упевненості.
4. Передавання JSON: env, -D, --
На рівні щоденної роботи у SPRING_APPLICATION_JSON є три способи передавання. Нової системи пріоритетів тут не з’являється: змінюється лише спосіб доставки одного й того самого рядка до Boot. І так, тут починається найвеселіше: екранування лапок. Вважайте це мініподатком за компактність.
Через env var SPRING_APPLICATION_JSON
В Unix-подібних оболонках зазвичай найпростіше використати одинарні лапки зовні, а всередині JSON лишити подвійні:
# Один JSON-блок замість набору окремих змінних середовища
SPRING_APPLICATION_JSON='{"app":{"catalog":{"title":"Demo Catalog","maintenance-mode":true}}}' \
./gradlew bootRun
Якщо у вас Windows PowerShell, підхід схожий, лише синтаксис присвоєння інший:
# PowerShell: задаємо змінну середовища й запускаємо застосунок
$env:SPRING_APPLICATION_JSON = '{"app":{"catalog":{"title":"Demo Catalog","maintenance-mode":true}}}'
./gradlew bootRun
Головна ідея: ми передаємо одну змінну, але всередині неї — одразу кілька пов’язаних налаштувань.
Через -Dspring.application.json=...
Іноді зручніше передати це як параметр JVM, особливо якщо ви запускаєте застосунок як звичайну команду Java і хочете, щоб налаштування працювали саме на рівні JVM (або так простіше налаштувати в IDE).
Приклад виглядає так:
# Важливо: -D... — це параметр JVM, він має стояти до -jar
java -Dspring.application.json='{"server":{"port":9095}}' -jar app.jar
Тут важливий момент із серії «помилка на мільйон»: -D... — це параметр JVM, і він має стояти до -jar. Якщо поставити його після, JVM його не побачить. Вона не образиться, вона просто проігнорує — і ви знову будете сперечатися з реальністю.
Через --spring.application.json=...
Boot уміє приймати це і як аргумент застосунку. На вигляд це взагалі схоже на звичайний --key=value, тільки значення — JSON:
# Gradle: аргументи застосунку передаються через --args (і тут зазвичай починаються лапки)
./gradlew bootRun --args='--spring.application.json={"app":{"catalog":{"title":"CLI JSON title"}}}'
Це працює, але найчастіше стає найпримхливішим щодо лапок варіантом. Якщо ви не любите екранувати лапки й сперечатися з shell, env var зазвичай спокійніший.
5. Пріоритети і null
SPRING_APPLICATION_JSON легко переоцінити: задали APP_CATALOG_TITLE, не спрацювало, а потім виявляється, що вище стояв JSON-блок. Тому тут корисно зафіксувати не всю карту заново, а лише місце цього джерела: аргументи командного рядка зазвичай сильніші, далі йде JSON-блок, потім системні властивості й змінні середовища, а вже потім файли.
Давайте розберемо один ключ app.catalog.title у п’яти місцях, щоб побачити поведінку наочно:
1) В application.yaml лежить значення за замовчуванням:
app:
catalog:
# Значення за замовчуванням із файлу конфігурації
title: "Title from file"
2) Ви додали змінну середовища:
APP_CATALOG_TITLE="Title from env"
3) Ви додали JSON-блок:
SPRING_APPLICATION_JSON='{"app":{"catalog":{"title":"Title from JSON"}}}'
4) І ще зверху задали аргумент командного рядка:
--app.catalog.title="Title from CLI"
Підсумкове значення буде "Title from CLI", бо аргументи командного рядка переможуть усіх.
Тепер важливий нюанс про null. Новачки часто очікують, що так можна «скинути» властивість:
# Важливо: null тут не працює як «скидання» або «стертя», а радше як «ключ відсутній»
SPRING_APPLICATION_JSON='{"app":{"catalog":{"title":null}}}'
Очікування: «ну я ж задав null, отже воно порожнє». Реальність: для цього механізму null не працює як видалення. У підсумку у вас залишиться значення з слабшого джерела (наприклад, з application.yaml), тому що ключ із null вважається по суті відсутнім. Тобто null — це не кнопка Reset.
Якщо потрібно повернути значення за замовчуванням, найнадійніший шлях — просто не задавати перевизначення. Конфігурація в Boot взагалі любить просту філософію: «якщо не знаєш, що робити, прибери зайве — і стане зрозуміліше».
6. Мінідіагностика через Environment
Коли починається конфлікт джерел, мозок намагається «пригадати», що ви запускали, що було в терміналі, що IDE підсовує в Run Configuration, і де взагалі істина. У цей момент найкраще не гадати, а запитати в застосунку напряму: «Що ти бачиш у Environment?».
Нижче — той самий тимчасовий probe-runner, лише з ключами, які зручно перевірити саме для JSON-перевизначення. Не тримайте його постійно в проєкті: це просто ліхтарик, щоб швидко побачити, хто переміг у ланцюжку пріоритетів.
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
// Навчальний хелпер: друкуємо підсумкові значення з Environment, щоб побачити пріоритет джерел
@Bean
ApplicationRunner configEchoRunner(Environment env) {
return args -> {
// getProperty(...) зазвичай повертає рядок або null, якщо ключ не заданий у жодному джерелі
System.out.println("app.catalog.title = " + env.getProperty("app.catalog.title"));
// Тут ключ із дефісом — це нормально: його було «розплющено» з JSON/YAML у app.catalog.maintenance-mode
System.out.println("maintenance = " + env.getProperty("app.catalog.maintenance-mode"));
// Зручний маркер, щоб перевірити, яке саме джерело перевизначило server.port
System.out.println("server.port = " + env.getProperty("server.port"));
};
}
Якщо ви запустите застосунок із таким JSON:
SPRING_APPLICATION_JSON='{"app":{"catalog":{"title":"JSON title","maintenance-mode":true}},"server":{"port":9095}}' \
./gradlew bootRun
то вивід буде приблизно таким (значення — для прикладу):
app.catalog.title = JSON title // значення з JSON
maintenance = true // значення з JSON
server.port = 9095 // порт сервера
Суть тут у тому, що застосунок перестає бути «чорною скринькою». Ви більше не сперечаєтеся з YAML-файлом на диску. Ви питаєте в Environment, і він показує підсумкову правду.
7. Типові помилки під час використання SPRING_APPLICATION_JSON
Ця тема здається маленькою й «ніби очевидною», доки не трапляється перший запуск, у якому JSON не розібрався, shell з’їв лапки, а застосунок тихо стартував із значеннями за замовчуванням — і ви впевнені, що Boot ігнорує вас зі шкідливості. Насправді майже всі проблеми тут типові й вирішуються уважністю до синтаксису та пріоритетів. Розберімо найчастіші граблі.
Помилка №1: JSON невалідний (зайва кома, лапка без екранування, коментар).
YAML уміє бути «людянішим» і іноді прощає дрібні огріхи, а JSON — ні. Особливо часто ламають конфіг зайва кома в кінці ({"a":1,}) і спроба вставити коментар. Лікується нудно: перевіркою JSON на валідність і звичкою тримати його максимально компактним.
Помилка №2: shell “з’їв” лапки, і до Boot дійшла каша.
У Bash і PowerShell правила лапок різні, а у Windows CMD — ще заплутаніші. Якщо ви бачите, що значення ніби не застосовується, перше, що варто зробити, — вивести змінну середовища в тому самому терміналі й переконатися, що в ній справді лежить коректний рядок JSON, а не обрізаний фрагмент.
Помилка №3: спроба зберігати великий набір налаштувань одним JSON-рядком.
SPRING_APPLICATION_JSON добрий, коли ви перевизначаєте 2–5 пов’язаних значень. Коли ви намагаєтеся запхнути туди половину application.yaml, у вас зникає читабельність, зростає ризик помилок, і будь-які зміни перетворюються на «редагування замінованого рядка». Для постійної конфігурації краще й надалі підходять файли.
Помилка №4: очікування, що null “скине” значення з файла.
Інтуїтивно здається, що null — це «порожньо», отже перевизначення має перемогти. Але для цього механізму null не працює як стирання ключа. У підсумку ви думаєте, що “обнулили”, а застосунок бере значення знизу за пріоритетом. Якщо потрібно прибрати перевизначення — приберіть саму перевизначену властивість.
Помилка №5: переплутані пріоритети джерел (особливо аргументи командного рядка поверх JSON).
Дуже частий сценарій: ви задали JSON-блок і впевнені, що він головний, а потім в IDE або в команді запуску десь заховався --app.catalog.title=.... І він переміг. У такі моменти допомагає або друк підсумкових значень через Environment, або просто дисципліна: у конфліктних ситуаціях завжди спершу дивимося на фактичну команду запуску.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ