</>
Back to Blog
Format 2026-05-30 9 min read

YAML and .properties, Properly Explained

YAML 1.0 was published in 2001 by Clark Evans, Ingy döt Net, and Oren Ben-Kiki, designed to be 'human-friendly' where XML was machine-friendly and JSON didn't yet exist as a separate format. The vision was beautiful: a config file that looks like prose. The reality is a format whose 1.1 spec contains a coercion table responsible for more outages than any single feature in any other config format, and whose 1.2 spec fixes most of them — except your tooling probably still implements 1.1.

YAMLYAML 1.1YAML 1.2.propertiesNorway ProblemConfiguration

YAML and Java's .properties format both solve the same problem — configuration as text — and they took opposite paths. YAML maximized expressivity (nested structures, anchors, multi-line strings, tagged types). .properties maximized boredom (one key, one value, one line). The world has spent twenty years discovering that the boring one was right more often than anyone wanted to admit.

YAML in one paragraph

YAML is a superset of JSON. Every valid JSON document is valid YAML 1.2. On top of JSON's grammar, YAML adds: indentation-based nesting, unquoted strings, multi-line strings, comments (#), references via anchors and aliases (&id / *id), explicit type tags (!!str, !!binary), and a parade of implicit type coercions that converted strings into numbers, booleans, and dates without you asking.

The format reads like a stripped-down config DSL. Empty config files are valid. A single string is a valid document. Lists, maps, and scalars compose like Lego.

service: api-gateway
replicas: 3
flags:
  - logging
  - tracing
env:
  LOG_LEVEL: info
  TIMEOUT: 30s

That's the surface YAML. The trouble is everything underneath.

The Norway Problem

This is the bug that defined YAML 1.1's reputation.

countries:
  - GB
  - IE
  - NO
  - SE

In YAML 1.1, that document parses as:

{'countries': ['GB', 'IE', False, 'SE']}

NO is not a country code in YAML 1.1; it's a boolean false. So is No, no, N, n, false, False, FALSE, off, Off, OFF. The boolean-coercion table accepted a comically wide range of inputs. Norway's two-letter country code happened to be one of them. Stack Overflow questions, library bug reports, and at least one widely-reported deployment incident all resolved to the same root cause: YAML 1.1 thought NO was a boolean.

YAML 1.2, published in 2009, fixed this — booleans are now strictly true and false. But here's the catch: many parsers in the wild still default to YAML 1.1 semantics, including PyYAML's default loader for years, and several Ruby libraries, and the YAML in some build systems. The fix is to use a 1.2-strict loader (PyYAML's safe_load plus Loader=yaml.CSafeLoader, or ruamel.yaml), but you have to know to ask.

The other implicit-coercion footguns

The Norway Problem is only the most famous. YAML 1.1 also coerced:

  • 12:34:56 as a sexagesimal number: 12*3600 + 34*60 + 56 = 45296. Time-of-day strings became integers.
  • 0123 as octal 83. Anything starting with a leading zero might be octal.
  • 1.0 as a float, 1.0e2 as 100.0. Version strings written as 1.0 became floats and lost trailing-zero formatting.
  • yes, no, on, off as booleans. Yes-or-no answers became truthy/falsy in unpredictable cases.
  • null, Null, NULL, ~, (empty) all as null.
  • 2010-04-01 as a date object. Version numbers that happen to look like dates became dates.

The pattern: any value that looks like a special type is that type, unless quoted. The rule for safety is therefore: quote anything that could plausibly be misinterpreted. version: "1.0", code: "NO", time: "12:34:56", port: "0123". The cost is mild visual noise; the benefit is no surprise type coercions.

YAML 1.2 narrowed the implicit-type set significantly: only true/false, null/~/empty, integers, and floats are coerced. NO, yes, 12:34:56, 2010-04-01 are all strings. But again — if your parser is in 1.1 mode, this doesn't help you.

Indentation: significant, in the worst way

Python's significant indentation is mostly fine because Python's parser tells you immediately when indentation is wrong. YAML's significant indentation is treacherous because YAML accepts many indentation choices as valid but with different meanings.

items:
  - name: alpha
    qty: 3
  - name: beta
    qty: 5

vs.

items:
  - name: alpha
  qty: 3
  - name: beta
  qty: 5

The second version is also a valid YAML document, but qty is now a sibling of items, not a property of each item. The parser doesn't tell you you got it wrong; it just gives you a document with a different shape, and your application explodes downstream.

Tabs are also banned for indentation in YAML, and most editors that auto-convert tabs to spaces will silently fix this — or, occasionally, silently mix them, in which case the parser bails with a cryptic error.

The pragmatic rule: lint your YAML with a real linter (yamllint, kubeval for Kubernetes specifically) before deploying. The format gives you no compile-time check; you have to bolt one on.

Anchors and aliases: the feature you'll regret using

YAML lets you reference a node from elsewhere in the document:

defaults: &defaults
  timeout: 30
  retries: 3

services:
  api:
    <<: *defaults
    port: 8080
  worker:
    <<: *defaults
    port: 9000

&defaults declares an anchor; *defaults references it; <<: merges the anchored map into the current map. It looks DRY, it scales reasonably, and it fails in three ways:

  1. It composes with multi-document streams oddly. Anchors are per-document; carrying them across documents requires extension support.
  2. It produces output that's not safe to round-trip through arbitrary tools. Many tools resolve aliases on read, so the dumped output loses the structure.
  3. It enables denial-of-service attacks when an attacker can author the YAML — the YAML equivalent of XML's billion laughs:
a: &a [1,1,1,1,1,1,1,1,1]
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b]
# ... by g, you've allocated 10^9 ints

Most modern parsers cap alias expansion. Don't disable that cap. Don't accept anchored YAML from untrusted sources unless your parser explicitly safe-handles it.

Multi-document streams

A single YAML file can contain multiple documents separated by ---:

---
kind: Deployment
metadata:
  name: api
---
kind: Service
metadata:
  name: api-svc

This is heavily used by Kubernetes and Helm. The format is part of the YAML spec; many YAML libraries return a sequence (Python yaml.safe_load_all, Go yaml.NewDecoder().Decode in a loop), and the optional trailing ... end-of-document marker is rarely seen but valid.

Multi-line strings: more flavors than you want

YAML has five ways to write a string spanning multiple lines, and they differ in whitespace handling:

literal: |       # preserve newlines
  line one
  line two

folded: >        # collapse newlines into spaces
  line one
  line two

literal-strip: |-   # preserve newlines, strip trailing
  line one

literal-keep: |+    # preserve newlines, keep all trailing
  line one


double-quoted: "line one\nline two\n"   # JSON-style escapes work

The | literal block keeps newlines; the > folded block joins them with spaces (except blank lines, which become real newlines). The - chomping indicator strips trailing newlines, + keeps all of them, default keeps exactly one. Get the chomping wrong and your shell-script-in-YAML produces an extra newline that breaks the script.

.properties: the boring format that wouldn't die

Java's .properties format predates the millennium and has barely changed. The grammar:

key=value
key.with.dots=another value
key.with.spaces = padding around the equals is okay
multi.line=one line\
            second line continued
empty.value=
# comment line
! also a comment line
key:value           # colon also separates

That's most of it. Keys are strings; values are strings; nesting doesn't exist; you fake hierarchy with dotted keys. Encoding is officially ISO-8859-1, with \uXXXX escapes for anything else (Java Properties files are a fossilized window into pre-UTF-8 i18n).

Why it survived: it's impossible to misparse. There is no implicit coercion. There is no boolean Norway. There are no anchors. There is no indentation to get wrong. The parser is twenty lines of code in any language. Spring, Java EE, Maven, Gradle, log4j, Kafka, Hadoop, JBoss — the entire JVM ecosystem standardized on it because it was simple and stayed simple.

The downsides: no native nesting, no native lists, no comments-on-the-same-line-as-a-value, ISO-8859-1 by default. Modern Spring and Spring Boot accept both .properties and YAML; most teams pick YAML for new code and live with .properties for legacy.

Converting between them

Conceptually:

  • a.b.c=value in .properties{a: {b: {c: value}}} in YAML.
  • a.b[0]=x (Spring's list extension) → {a: {b: [x]}}.
  • A YAML map flattens to dotted keys; a YAML list flattens to indexed keys (a[0], a[1]).

Two pitfalls:

  1. Type loss in either direction. YAML's enabled: true is the boolean true. The .properties equivalent is enabled=true, which is the string "true". The consumer has to know to coerce.
  2. .properties allows keys with dots that aren't structural. web.url=http://... could mean "a key called web.url" or "the url child of web." YAML conversion has to make a choice; the right choice is application-specific.

The conversion is mostly mechanical, and the in-browser tool here implements it both ways with the standard flattening rules. It won't recover information that wasn't expressible in the source — there's no way for .properties to encode a YAML document with anchor references — but for the common case of flat-config-vs-nested-config, the round-trip is clean.

Common pitfalls

  • YAML 1.1 booleans treating NO, Yes, Off as booleans. Quote them or upgrade your parser.
  • Leading zeros read as octal in 1.1. Quote phone numbers, ZIP codes, account numbers — anything that's a string of digits but not arithmetic.
  • 12:34 read as sexagesimal. Quote times.
  • Tab vs space indentation. YAML rejects tabs.
  • Wrong indentation level parses as a different document shape, silently. Lint.
  • Unquoted dates become date objects, not strings. Version strings like 2024-06 are at risk.
  • null, ~, empty value all mean null. An empty key: is null, not an empty string. Use key: "" if you want empty.
  • Multi-line block chomping producing an extra newline you didn't want. Use |- if your downstream consumer is whitespace-sensitive.
  • .properties line continuations with \ at end of line require leading-whitespace stripping on the next line; some hand-rolled parsers get this wrong.
  • .properties Unicode when you forgot to \uXXXX-escape non-ASCII characters in an ISO-8859-1 file. Modern Spring accepts UTF-8 if you tell it; check your loader.

When to use which

  • YAML for: Kubernetes manifests, GitHub Actions workflows, Ansible playbooks, anywhere the existing ecosystem expects YAML, anywhere humans need to author multi-level config.
  • .properties for: Spring config, Java application properties, anywhere the consumer is JVM-side and the structure is flat-ish.
  • TOML when you have the freedom to choose. It's what YAML wishes it had been: human-readable, comments, no implicit type coercion, no significant indentation. Used by Cargo, pyproject.toml, many newer tools.
  • JSON for everything machine-to-machine, including config files generated by tools.
  • Environment variables for the smallest, flattest, most transport-agnostic configuration. Twelve-factor was right about this.

If you can choose, choose TOML for new human-authored configs. If the ecosystem made the choice for you (Kubernetes, GitHub Actions, Spring), use what it expects, quote your strings defensively, lint your files in CI, and never trust YAML 1.1 with a country code.

Convert between YAML and .properties

The YAML / .properties tool on this site converts in both directions, in the browser, with structure-preserving flattening. Useful when migrating between Spring config and Kubernetes / GitHub Actions YAML. Nothing leaves your browser.

Open the YAML tool

Related guides

Keep the session useful with adjacent reading instead of exiting after one article.

View all guides

Cookie Consent

We use cookies to enhance your experience and show relevant ads. You can customize your preferences.