YAML #
YAML (YAML Ain’t Markup Language) adalah format serialisasi data yang dirancang untuk mudah dibaca manusia. Dibanding JSON yang menggunakan kurung kurawal dan tanda kutip, YAML menggunakan indentasi dan tanda titik dua — lebih bersih untuk file konfigurasi yang panjang. Kamu sudah sering melihat YAML tanpa menyadarinya: application.yml di Spring Boot, docker-compose.yml, kubernetes.yaml, pipeline CI/CD. Di Java, library utama untuk bekerja dengan YAML adalah SnakeYAML — yang juga dipakai Spring Boot di balik layar. Artikel ini membahas sintaks YAML dari dasar, cara membaca dan menulis YAML dengan SnakeYAML, integrasi penuh dengan Spring Boot, penanganan multi-dokumen, anchors dan aliases untuk menghindari duplikasi, serta validasi konfigurasi dengan Bean Validation.
Sintaks YAML Dasar #
Sebelum masuk ke kode Java, kamu perlu memahami cara YAML bekerja. Aturan utama YAML hanya ada satu: indentasi menentukan hierarki, dan indentasi harus konsisten (spasi, bukan tab).
Tipe Data dan Struktur Dasar #
# Komentar dimulai dengan #
# Skalar (nilai tunggal)
nama: Budi Santoso
umur: 30
tinggi: 175.5
aktif: true
tidak_ada: null # atau ~
# String bisa tanpa tanda kutip (sebagian besar kasus)
kota: Jakarta
# String dengan karakter khusus harus dikutip
pesan: "Halo: dunia" # titik dua perlu dikutip
path: 'C:\Users\Budi' # backslash aman dengan single quote
# Multi-baris dengan | (literal — newline dipertahankan)
deskripsi: |
Baris pertama.
Baris kedua.
Baris ketiga.
# Multi-baris dengan > (folded — newline jadi spasi)
ringkasan: >
Ini adalah ringkasan panjang
yang akan digabungkan menjadi
satu paragraf dengan spasi.
# Mapping (dictionary / object)
alamat:
jalan: Jl. Merdeka No. 1
kota: Jakarta
kode_pos: 10110
# Sequence (list / array)
hobi:
- membaca
- coding
- bersepeda
# Sequence inline
warna: [merah, hijau, biru]
# Mapping inline
koordinat: {lat: -6.2, lng: 106.8}
# Nested — mapping di dalam sequence
produk:
- id: 1
nama: Laptop
harga: 12000000
- id: 2
nama: Mouse
harga: 150000
Tipe Data Otomatis #
YAML mendeteksi tipe data secara otomatis — ini bisa menjadi jebakan jika tidak hati-hati:
# Angka
port: 8080 # Integer
rasio: 3.14 # Float
besar: 1_000_000 # Integer dengan separator (1000000)
hex: 0xFF # Hexadecimal (255)
oktal: 0o17 # Octal (15)
# Boolean — YAML 1.1 menganggap ini semua boolean
ya_bool: true # true
tidak_bool: false # false
# HATI-HATI: di YAML 1.1, 'yes', 'no', 'on', 'off' juga boolean!
# Untuk menghindari ambiguitas, selalu kutip string yang bisa salah tafsir
iya_string: "yes" # string "yes", bukan boolean
# Null
kosong: null
juga_kosong: ~
tidak_ada: # nilai kosong juga null
# Tanggal (SnakeYAML otomatis parse ini sebagai java.util.Date)
tanggal: 2025-08-17 # parsed sebagai Date
waktu: 2025-08-17T09:00:00Z # parsed sebagai Date dengan waktu
Anchors dan Aliases — Hindari Duplikasi #
Salah satu fitur YAML yang tidak ada di JSON adalah kemampuan mendefinisikan nilai sekali dan mereferensikannya di tempat lain.
# Definisikan anchor dengan &nama
database_default: &db_default
host: localhost
port: 5432
username: admin
pool_size: 10
# Gunakan alias dengan *nama — menyalin semua field dari anchor
development:
database:
<<: *db_default # merge semua field dari db_default
name: dev_db # override field tertentu
production:
database:
<<: *db_default
host: db.production.com # override host
name: prod_db
pool_size: 50 # override pool_size
# Hasil production.database setelah merge:
# host: db.production.com
# port: 5432
# username: admin
# pool_size: 50
# name: prod_db
Multi-Dokumen #
Satu file YAML bisa berisi beberapa dokumen yang dipisahkan dengan ---:
# Dokumen 1
---
nama: Budi
role: admin
# Dokumen 2
---
nama: Ani
role: user
# Dokumen 3
---
nama: Citra
role: moderator
SnakeYAML #
SnakeYAML adalah library Java untuk membaca dan menulis YAML. Ia adalah parser YAML paling banyak dipakai di ekosistem Java dan menjadi dependensi transitif Spring Boot.
Dependensi #
<!-- Maven -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.2</version>
</dependency>
// Gradle
implementation 'org.yaml:snakeyaml:2.2'
Di proyek Spring Boot, SnakeYAML sudah tersedia otomatis lewat spring-boot-starter — tidak perlu tambah dependensi.
Membaca YAML #
import org.yaml.snakeyaml.Yaml;
import java.io.InputStream;
import java.util.Map;
import java.util.List;
Yaml yaml = new Yaml();
// Baca dari string
String konten = """
nama: Budi
umur: 30
aktif: true
hobi:
- membaca
- coding
""";
Map<String, Object> data = yaml.load(konten);
String nama = (String) data.get("nama"); // "Budi"
int umur = (Integer) data.get("umur"); // 30
boolean aktif = (Boolean) data.get("aktif"); // true
List<String> hobi = (List<String>) data.get("hobi"); // ["membaca", "coding"]
// Baca dari file
try (InputStream is = new FileInputStream("konfigurasi.yaml")) {
Map<String, Object> config = yaml.load(is);
System.out.println(config);
}
// Baca dari classpath (untuk resource dalam JAR)
try (InputStream is = getClass().getResourceAsStream("/config.yaml")) {
Map<String, Object> config = yaml.load(is);
}
Membaca ke Objek Java (POJO) #
SnakeYAML bisa langsung mengisi POJO — nama field YAML harus cocok dengan nama field Java (atau getter/setter).
// POJO yang akan diisi
public class DatabaseConfig {
private String host;
private int port;
private String name;
private String username;
private String password;
private int poolSize;
// Getter dan setter wajib ada untuk SnakeYAML
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
// ... setter dan getter lainnya
}
// YAML
String yamlConfig = """
host: localhost
port: 5432
name: mydb
username: admin
password: rahasia123
poolSize: 10
""";
// Load langsung ke kelas tertentu
Yaml yaml = new Yaml(new Constructor(DatabaseConfig.class, new LoaderOptions()));
DatabaseConfig config = yaml.load(yamlConfig);
System.out.println(config.getHost()); // localhost
System.out.println(config.getPort()); // 5432
System.out.println(config.getPoolSize()); // 10
Membaca Multi-Dokumen #
String multiDoc = """
---
nama: Budi
role: admin
---
nama: Ani
role: user
---
nama: Citra
role: moderator
""";
Yaml yaml = new Yaml();
// loadAll() mengembalikan Iterable atas semua dokumen
for (Object doc : yaml.loadAll(multiDoc)) {
Map<String, Object> pengguna = (Map<String, Object>) doc;
System.out.println(pengguna.get("nama") + ": " + pengguna.get("role"));
}
// Budi: admin
// Ani: user
// Citra: moderator
Menulis YAML #
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.DumperOptions;
// Konfigurasi output
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); // block style (bukan inline)
options.setIndent(2); // indentasi 2 spasi
options.setPrettyFlow(true);
Yaml yaml = new Yaml(options);
// Tulis Map ke YAML
Map<String, Object> data = new LinkedHashMap<>(); // LinkedHashMap agar urutan terjaga
data.put("nama", "Laptop");
data.put("harga", 12_000_000);
data.put("tersedia", true);
data.put("spesifikasi", Map.of("ram", "16GB", "storage", "512GB SSD"));
data.put("tag", List.of("elektronik", "komputer"));
String yamlOutput = yaml.dump(data);
System.out.println(yamlOutput);
/*
nama: Laptop
harga: 12000000
tersedia: true
spesifikasi:
ram: 16GB
storage: 512GB SSD
tag:
- elektronik
- komputer
*/
// Tulis ke file
try (FileWriter writer = new FileWriter("output.yaml")) {
yaml.dump(data, writer);
}
// Tulis POJO langsung
DatabaseConfig config = new DatabaseConfig();
config.setHost("localhost");
config.setPort(5432);
String pojoYaml = yaml.dump(config);
// !!com.example.DatabaseConfig
// host: localhost
// port: 5432
// ...
Konfigurasi LoaderOptions dan DumperOptions #
import org.yaml.snakeyaml.LoaderOptions;
// LoaderOptions — atur batasan keamanan
LoaderOptions loaderOptions = new LoaderOptions();
loaderOptions.setMaxAliasesForCollections(50); // batas alias dalam koleksi
loaderOptions.setAllowDuplicateKeys(false); // tolak kunci duplikat
loaderOptions.setCodePointLimit(10 * 1024 * 1024); // batas ukuran file (10MB)
// Penting untuk keamanan: batasi tipe yang bisa diinstansiasi
loaderOptions.setTagInspector(tag -> {
// Hanya izinkan tipe dari paket kita sendiri
return tag.startsWith("tag:yaml.org,2002:") ||
tag.contains("com.example.");
});
Yaml yaml = new Yaml(new Constructor(new LoaderOptions()));
SnakeYAML sebelum versi 2.0 rentan terhadap serangan YAML deserialization — penyerang bisa menyisipkan tipe Java berbahaya dalam YAML dan menyebabkan eksekusi kode sembarangan. Selalu gunakanLoaderOptionsdenganSafeConstructoratau batasi tipe yang diizinkan saat memproses YAML dari sumber yang tidak tepercaya.
YAML dengan Jackson #
Jackson (yang sudah dibahas di artikel JSON) juga bisa membaca dan menulis YAML dengan modul tambahan. Ini berguna jika kamu sudah menggunakan Jackson untuk JSON dan ingin API yang konsisten untuk YAML.
Dependensi #
<!-- Maven -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.17.1</version>
</dependency>
Membaca dan Menulis YAML dengan Jackson #
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
// Buat ObjectMapper khusus YAML
ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
// POJO
record AppConfig(String appName, int port, DatabaseConfig database) {}
record DatabaseConfig(String host, int port, String name) {}
String yaml = """
appName: Aplikasi Saya
port: 8080
database:
host: localhost
port: 5432
name: mydb
""";
// Deserialisasi YAML → POJO (sama persis dengan JSON)
AppConfig config = yamlMapper.readValue(yaml, AppConfig.class);
System.out.println(config.appName()); // Aplikasi Saya
System.out.println(config.database().host()); // localhost
// Serialisasi POJO → YAML
AppConfig baru = new AppConfig("App Baru", 9090,
new DatabaseConfig("db.server.com", 5432, "prod_db"));
// Konfigurasi output
ObjectMapper rapi = new ObjectMapper(
new YAMLFactory()
.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER) // hilangkan "---"
.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) // kurangi tanda kutip
);
String yamlOutput = rapi.writerWithDefaultPrettyPrinter().writeValueAsString(baru);
System.out.println(yamlOutput);
// Anotasi Jackson bekerja juga di YAML!
public class Produk {
@JsonProperty("product_name") // field YAML: "product_name"
private String nama;
@JsonIgnore
private String internal;
}
Keunggulan menggunakan Jackson untuk YAML dibanding SnakeYAML langsung: semua anotasi Jackson (@JsonProperty, @JsonIgnore, @JsonInclude, dll.) bekerja secara transparan, dan kamu bisa berbagi logika serialisasi antara JSON dan YAML.
Spring Boot — application.yml #
Spring Boot mendukung application.yml sebagai alternatif dari application.properties. YAML lebih cocok untuk konfigurasi bertingkat karena hierarki terlihat lebih jelas tanpa pengulangan prefix.
Perbandingan application.properties vs application.yml #
# application.properties — prefix berulang
server.port=8080
server.servlet.context-path=/api
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=admin
spring.datasource.password=rahasia
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=2
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=false
# application.yml — hierarki bersih, tidak ada pengulangan
server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: admin
password: rahasia
hikari:
maximum-pool-size: 10
minimum-idle: 2
jpa:
hibernate:
ddl-auto: validate
show-sql: false
Profil di YAML #
Salah satu keunggulan application.yml adalah bisa mendefinisikan semua profil dalam satu file menggunakan separator --- dan spring.config.activate.on-profile:
# Konfigurasi default (berlaku untuk semua profil kecuali ditimpa)
app:
nama: Aplikasi Java
versi: 1.0.0
server:
port: 8080
logging:
level:
root: INFO
---
# Profil development
spring:
config:
activate:
on-profile: development
server:
port: 8081
spring:
datasource:
url: jdbc:h2:mem:devdb
username: sa
password:
h2:
console:
enabled: true
logging:
level:
com.example: DEBUG
---
# Profil production
spring:
config:
activate:
on-profile: production
server:
port: 80
spring:
datasource:
url: jdbc:postgresql://db.server.com:5432/proddb
username: ${DB_USERNAME} # nilai dari environment variable
password: ${DB_PASSWORD}
logging:
level:
root: WARN
# Aktifkan profil saat menjalankan
java -jar app.jar --spring.profiles.active=production
# atau lewat environment variable
SPRING_PROFILES_ACTIVE=production java -jar app.jar
Konfigurasi Kustom dengan @ConfigurationProperties #
Cara terbaik untuk membaca konfigurasi YAML di Spring Boot adalah dengan @ConfigurationProperties — ia mem-bind hierarki YAML ke POJO secara otomatis.
# application.yml
app:
nama: Toko Online
versi: 2.5.0
fitur:
registrasi: true
pembayaran-digital: true
notifikasi-email: false
batas:
upload-mb: 10
request-per-menit: 100
kontak:
email: [email protected]
telepon: "+62-21-12345678"
database:
host: localhost
port: 5432
nama: tokodb
pool:
min: 2
max: 20
timeout-detik: 30
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import jakarta.validation.constraints.*;
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
@NotBlank
private String nama;
@NotBlank
private String versi;
private Fitur fitur = new Fitur();
private Batas batas = new Batas();
private Kontak kontak = new Kontak();
private Database database = new Database();
// Nested classes
public static class Fitur {
private boolean registrasi = true;
private boolean pembayaranDigital = true; // YAML: pembayaran-digital
private boolean notifikasiEmail = false;
// getter dan setter
}
public static class Batas {
@Min(1) @Max(100)
private int uploadMb = 5;
@Min(1)
private int requestPerMenit = 60;
// getter dan setter
}
public static class Kontak {
@Email
private String email;
@Pattern(regexp = "\\+?[0-9-]+")
private String telepon;
// getter dan setter
}
public static class Database {
@NotBlank
private String host;
@Min(1) @Max(65535)
private int port = 5432;
@NotBlank
private String nama;
private Pool pool = new Pool();
public static class Pool {
@Min(1)
private int min = 2;
@Min(1)
private int max = 10;
@Min(1)
private int timeoutDetik = 30;
// getter dan setter
}
// getter dan setter
}
// Getter dan setter untuk semua field...
}
// Aktifkan validasi @ConfigurationProperties
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class Aplikasi {
public static void main(String[] args) {
SpringApplication.run(Aplikasi.class, args);
}
}
// Gunakan di komponen lain lewat dependency injection
@Service
public class InfoService {
private final AppProperties props;
public InfoService(AppProperties props) {
this.props = props;
}
public String getInfo() {
return props.getNama() + " v" + props.getVersi();
}
}
Variabel Lingkungan dan Placeholder #
# Referensi environment variable dengan ${NAMA_VAR}
spring:
datasource:
password: ${DB_PASSWORD} # wajib ada
username: ${DB_USER:admin} # default "admin" jika tidak ada
# Referensi properti lain
app:
nama: Toko Saya
url-base: https://toko.com
url-api: ${app.url-base}/api # "https://toko.com/api"
url-docs: ${app.url-base}/docs # "https://toko.com/docs"
# Nilai dari file (Spring Boot)
spring:
datasource:
password: ${file:/run/secrets/db_password} # baca dari file
Validasi Konfigurasi #
@ConfigurationProperties mendukung Bean Validation (jakarta.validation) secara penuh. Jika konfigurasi tidak valid saat startup, aplikasi langsung gagal dengan pesan yang jelas — jauh lebih baik daripada error yang muncul di tengah eksekusi.
Menambahkan Validasi #
<!-- Maven — tambahkan jika belum ada (Spring Web sudah menyertakannya) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import jakarta.validation.constraints.*;
@ConfigurationProperties(prefix = "app")
@Validated // aktifkan validasi Bean Validation
public class AppProperties {
@NotBlank(message = "Nama aplikasi tidak boleh kosong")
private String nama;
@Min(value = 1, message = "Port harus lebih dari 0")
@Max(value = 65535, message = "Port tidak boleh melebihi 65535")
private int port;
@NotNull(message = "Konfigurasi email wajib diisi")
@Valid // validasi juga nested object
private EmailConfig email;
public static class EmailConfig {
@NotBlank
@Email(message = "Format email tidak valid")
private String pengirim;
@NotBlank
private String host;
@Min(1)
private int port = 587;
// getter dan setter
}
// getter dan setter
}
Jika ada nilai yang tidak valid saat startup:
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException:
Failed to bind properties under 'app' to AppProperties
Reason: app.port: Port harus lebih dari 0
app.email.pengirim: Format email tidak valid
YAML vs JSON vs Properties — Kapan Pakai Mana #
| Aspek | YAML | JSON | Properties |
|---|---|---|---|
| Keterbacaan | ✓ Sangat baik untuk hierarki | Sedang | Buruk untuk hierarki dalam |
| Komentar | ✓ Didukung | ✗ Tidak | ✓ Didukung |
| Tipe data | ✓ Otomatis | ✓ Eksplisit | ✗ Semua string |
| Multi-dokumen | ✓ Didukung | ✗ Tidak | ✗ Tidak |
| Anchor/alias | ✓ Hindari duplikasi | ✗ Tidak | ✗ Tidak |
| Parsing | Lebih lambat | Lebih cepat | Sangat cepat |
| Keamanan | Perlu hati-hati | Aman | Aman |
| Kegunaan utama | File konfigurasi, Kubernetes, CI/CD | REST API, data interchange | Konfigurasi Java sederhana |
Kapan Menggunakan YAML #
Gunakan YAML jika:
✓ File konfigurasi yang punya banyak hierarki (Spring Boot, Kubernetes, Docker Compose)
✓ Butuh komentar dalam file konfigurasi
✓ Ingin profil berbeda dalam satu file (dengan ---)
✓ Konfigurasi berulang yang bisa disederhanakan dengan anchor/alias
✓ Tim lebih nyaman membaca hierarki daripada properti datar
Gunakan JSON jika:
✗ Format data untuk REST API atau komunikasi antar layanan
✗ Konfigurasi yang akan dibaca oleh JavaScript / browser
✗ Tidak butuh komentar dan tipe otomatis
Gunakan Properties jika:
✗ Konfigurasi sederhana tanpa hierarki dalam
✗ Lingkungan yang hanya mendukung format lama
Hati-hati dengan YAML:
✗ Jangan proses YAML dari sumber tidak tepercaya tanpa SafeConstructor
✗ Tab tidak diizinkan untuk indentasi — selalu gunakan spasi
✗ Indentasi yang tidak konsisten menyebabkan error parsing yang membingungkan
✗ "yes", "no", "on", "off" diinterpretasikan sebagai boolean di YAML 1.1
Ringkasan #
- YAML menggunakan indentasi untuk hierarki — gunakan spasi (bukan tab), dan pastikan jumlah spasi konsisten. Indentasi yang salah adalah penyebab error YAML yang paling umum.
- SnakeYAML adalah library utama untuk parsing YAML di Java. Spring Boot menggunakannya di balik layar. Untuk penggunaan langsung, selalu konfigurasi
LoaderOptionsdenganSafeConstructorsaat memproses YAML dari sumber eksternal.- Jackson + YAMLFactory memungkinkan API yang konsisten antara JSON dan YAML — semua anotasi Jackson (
@JsonProperty,@JsonIgnore, dll.) bekerja untuk YAML juga.application.ymllebih bersih dariapplication.propertiesuntuk konfigurasi bertingkat karena tidak ada pengulangan prefix. Gunakan---untuk mendefinisikan beberapa profil dalam satu file.@ConfigurationPropertiesadalah cara terbaik membaca YAML di Spring Boot — ia mem-bind hierarki YAML ke POJO secara otomatis, mendukung tipe nested, list, dan map, serta bisa divalidasi dengan Bean Validation.- Variabel lingkungan via
${NAMA_VAR}— pisahkan konfigurasi sensitif (password, API key) ke environment variable. Gunakan${NAMA_VAR:nilai_default}untuk nilai fallback.- Anchors (
&nama) dan aliases (*nama) mencegah duplikasi konfigurasi — definisikan sekali, referensikan berkali-kali.<<:untuk merge seluruh mapping dengan override sebagian field.- “yes”, “no”, “on”, “off” adalah boolean di YAML 1.1 — gunakan tanda kutip jika ingin memperlakukan nilai ini sebagai string. Ini adalah jebakan YAML yang sangat umum.