YAML

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
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 gunakan LoaderOptions dengan SafeConstructor atau 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 #

AspekYAMLJSONProperties
Keterbacaan✓ Sangat baik untuk hierarkiSedangBuruk untuk hierarki dalam
Komentar✓ Didukung✗ Tidak✓ Didukung
Tipe data✓ Otomatis✓ Eksplisit✗ Semua string
Multi-dokumen✓ Didukung✗ Tidak✗ Tidak
Anchor/alias✓ Hindari duplikasi✗ Tidak✗ Tidak
ParsingLebih lambatLebih cepatSangat cepat
KeamananPerlu hati-hatiAmanAman
Kegunaan utamaFile konfigurasi, Kubernetes, CI/CDREST API, data interchangeKonfigurasi 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 LoaderOptions dengan SafeConstructor saat 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.yml lebih bersih dari application.properties untuk konfigurasi bertingkat karena tidak ada pengulangan prefix. Gunakan --- untuk mendefinisikan beberapa profil dalam satu file.
  • @ConfigurationProperties adalah 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.

← Sebelumnya: JSON   Berikutnya: MySQL →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact