Setiap aplikasi nontrivial membawa nilai yang tidak pantas ditulis di kode: port server, URL basis data, batas ukuran unggahan, tingkat log. Spring Boot menyediakan satu kanon untuk semuanya — berkas application.properties di src/main/resources — lengkap dengan mekanisme override berjenjang dari profil hingga variabel lingkungan. Pada Chapter ini kita pelajari dua format berkas, urutan prioritas sumber konfigurasi, dan cara mengikat kelompok properti menjadi tipe type-safe dengan @ConfigurationProperties.
Format Properties dan YAML #
Format .properties menyimpan pasangan kunci-nilai datar dengan titik sebagai pemisah hierarki. Format .yaml (atau .yml) menulis hierarki itu dengan indentasi:
spring.application.name=layanan-pesanan
spring.datasource.url=jdbc:h2:mem:demo
spring.datasource.username=sa
spring.datasource.hikari.maximum-pool-size=8
server.port=8080spring:
application:
name: layanan-pesanan
datasource:
url: jdbc:h2:mem:demo
username: sa
hikari:
maximum-pool-size: 8
server:
port: 8080Keduanya setara secara semantik — Spring Boot memuatnya dengan pustaka yang sama (SnakeYAML untuk YAML). Pilihan format adalah selera tim; .properties lebih tahan kesalahan indentasi, YAML lebih ringkas untuk hierarki dalam. Yang perlu diingat: hanya satu format yang dimuat untuk nama sama, dan kunci di .properties selalu menang atas YAML dengan nama sama karena urutan pemuatannya.
Urutan Prioritas Sumber Konfigurasi #
Kekuatan sebenarnya bukan pada formatnya, melainkan pada urutan prioritas yang baku. Ketika satu kunci muncul di beberapa sumber, sumber dengan prioritas lebih tinggi menimpa yang lebih rendah. Urutannya dari yang tertinggi:
argumen command line (--server.port=9090)
> variabel lingkungan (SPRING_DATASOURCE_URL)
> application-{profile}.properties
> application.propertiesDaftar lengkapnya di dokumentasi resmi mencakup lebih dari sepuluh sumber, termasuk SpringApplication.setDefaultProperties dan berkas eksternal di samping JAR. Desain ini memungkinkan satu artefak yang sama berperilaku berbeda per lingkungan tanpa rekompilasi — cukup lewat variabel lingkungan atau argumen startup.
Simulasi Override dan Binding #
Mekanisme relaxed binding menghubungkan penamaan sumber yang berbeda: variabel lingkungan SPRING_DATASOURCE_USERNAME dipetakan ke kunci spring.datasource.username. Program Java murni berikut merekonstruksi alurnya — memuat berkas, menerapkan override, lalu mengikat hasilnya ke record:
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class BacaKonfigurasi {
// Binding type-safe: mirror record @ConfigurationProperties(prefix = "spring.datasource")
record KonfigurasiDataSource(String url, String username, int maximumPoolSize) { }
public static void main(String[] args) {
// Isi application.properties yang ditulis pengembang.
List<String> applicationProperties = List.of(
"spring.application.name=layanan-pesanan",
"spring.datasource.url=jdbc:h2:mem:demo",
"spring.datasource.username=sa",
"spring.datasource.hikari.maximum-pool-size=8",
"server.port=8080");
Map<String, String> properti = new LinkedHashMap<>();
for (String baris : applicationProperties) {
int idx = baris.indexOf('=');
properti.put(baris.substring(0, idx), baris.substring(idx + 1));
}
// Variabel lingkungan menimpa berkas (relaxed binding):
// SPRING_DATASOURCE_USERNAME -> spring.datasource.username
String dariEnv = kunciDariEnv("SPRING_DATASOURCE_USERNAME");
properti.put(dariEnv, "budi_prod");
System.out.println("== konfigurasi efektif setelah override ==");
properti.forEach((k, v) -> System.out.println(k + " = " + v));
// Binding nilai ke record: satu tipe, sekali baca.
KonfigurasiDataSource ds = new KonfigurasiDataSource(
properti.get("spring.datasource.url"),
properti.get("spring.datasource.username"),
Integer.parseInt(properti.get("spring.datasource.hikari.maximum-pool-size")));
System.out.println();
System.out.println("== binding type-safe ke record ==");
System.out.println(ds);
System.out.println();
System.out.println("pool minimum 2 koneksi: "
+ (ds.maximumPoolSize() >= 2 ? "memenuhi" : "terlalu kecil"));
}
static String kunciDariEnv(String variabelLingkungan) {
return variabelLingkungan.toLowerCase().replace('_', '.');
}
}Output yang Dihasilkan:
== konfigurasi efektif setelah override ==
spring.application.name = layanan-pesanan
spring.datasource.url = jdbc:h2:mem:demo
spring.datasource.username = budi_prod
spring.datasource.hikari.maximum-pool-size = 8
server.port = 8080
== binding type-safe ke record ==
KonfigurasiDataSource[url=jdbc:h2:mem:demo, username=budi_prod, maximumPoolSize=8]
pool minimum 2 koneksi: memenuhiMari kita bedah baris-baris kuncinya:
- Loop
applicationProperties: tiap baris dipecah pada tanda=pertama denganindexOf, lalu dimasukkan keLinkedHashMapsehingga urutan cetak mengikuti urutan berkas. Ini adalah versi mini dariPropertySourcemilik Spring. kunciDariEnv("SPRING_DATASOURCE_USERNAME"): method mengubah huruf besar menjadi kecil dan garis bawah menjadi titik — aturan inti relaxed binding. Pemanggilanproperti.put(...)menimpa nilaisamenjadibudi_prod, persis perilaku environment variable yang menimpa berkas.record KonfigurasiDataSource(...): mirror dari@ConfigurationProperties(prefix = "spring.datasource"). Nilaimaximum-pool-size=8diurai denganInteger.parseIntke komponenint— konversi tipe yang dalam Spring nyata ditangani binder beserta durasi dan ukuran data.- Cetak
ds: record mencetak semua komponen terstruktur. Nilai konfigurasi yang tadinya tersebar di lima kunci kini terbaca sebagai satu objek dengan tipe yang benar.
Binding Type-Safe ke Record #
Untuk produksi, Spring Boot menawarkan binding deklaratif — ditampilkan apa adanya untuk Spring Boot 3.4/3.5:
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public record PropertiHikari(
int maximumPoolSize,
Duration connectionTimeout
) { }spring.datasource.hikari.maximum-pool-size=8
spring.datasource.hikari.connection-timeout=30sPerhatikan connection-timeout=30s terikat ke Duration — binder memahami sufiks waktu secara bawaan. Aktifkan binding ini dengan @EnableConfigurationProperties(PropertiHikari.class) atau @ConfigurationPropertiesScan, lalu injeksikan record tersebut sebagai bean biasa ke service yang membutuhkan. Salah ketik kunci atau nilai yang gagal dikonversi kini terdeteksi saat startup, bukan saat insiden di tengah malam.
Konfigurasi hanyalah nilai; Chapter berikutnya masuk ke lapisan pemrosesan request — alur kerja Spring MVC dari DispatcherServlet hingga JSON.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
BacaKonfigurasi.java
Unduh berkasimport java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class BacaKonfigurasi {
// Binding type-safe: mirror record @ConfigurationProperties(prefix = "spring.datasource")
record KonfigurasiDataSource(String url, String username, int maximumPoolSize) { }
public static void main(String[] args) {
// Isi application.properties yang ditulis pengembang.
List<String> applicationProperties = List.of(
"spring.application.name=layanan-pesanan",
"spring.datasource.url=jdbc:h2:mem:demo",
"spring.datasource.username=sa",
"spring.datasource.hikari.maximum-pool-size=8",
"server.port=8080");
Map<String, String> properti = new LinkedHashMap<>();
for (String baris : applicationProperties) {
int idx = baris.indexOf('=');
properti.put(baris.substring(0, idx), baris.substring(idx + 1));
}
// Variabel lingkungan menimpa berkas (relaxed binding):
// SPRING_DATASOURCE_USERNAME -> spring.datasource.username
String dariEnv = kunciDariEnv("SPRING_DATASOURCE_USERNAME");
properti.put(dariEnv, "budi_prod");
System.out.println("== konfigurasi efektif setelah override ==");
properti.forEach((k, v) -> System.out.println(k + " = " + v));
// Binding nilai ke record: satu tipe, sekali baca.
KonfigurasiDataSource ds = new KonfigurasiDataSource(
properti.get("spring.datasource.url"),
properti.get("spring.datasource.username"),
Integer.parseInt(properti.get("spring.datasource.hikari.maximum-pool-size")));
System.out.println();
System.out.println("== binding type-safe ke record ==");
System.out.println(ds);
System.out.println();
System.out.println("pool minimum 2 koneksi: "
+ (ds.maximumPoolSize() >= 2 ? "memenuhi" : "terlalu kecil"));
}
static String kunciDariEnv(String variabelLingkungan) {
return variabelLingkungan.toLowerCase().replace('_', '.');
}
}