The HOCON format
HOCON is a superset of JSON: every JSON document is valid HOCON, but HOCON adds comments, optional quoting, optional commas, and a handful of conveniences that make hand-written config pleasant.…
HOCON is a superset of JSON: every JSON document is valid HOCON, but HOCON adds comments, optional quoting, optional commas, and a handful of conveniences that make hand-written config pleasant. This page covers the syntax hocon parses today.
Objects and fields
The top-level braces are optional, and a field is a key, a separator (= or :), and a
value:
name = "Roamer"
version : "1.0"
= and : are interchangeable. Fields are separated by a newline or a comma; a trailing
comma is allowed:
a = 1, b = 2
c = 3
xs = [1, 2, 3,]
When a value is an object, the separator may be omitted entirely:
window {
width = 1024
height = 768
}
Comments
Both # and // start a comment that runs to the end of the line:
# a full-line comment
port = 8080 // a trailing comment
Values
hocon recognizes the JSON value types plus unquoted strings:
string = "quoted text"
bare = an-unquoted-string
number = 8080
float = 1.5
flag = true
empty = null
list = [1, "two", true]
nested = { a = 1, b = 2 }
true, false, and null are keywords; anything matching a number literal is a number;
everything else is a string.
Quoting
HOCON allows unquoted strings, but forbids these characters inside them:
$ " { } [ ] : = , + # ^ ? ! @ * & `. Most natural-language strings hit one of them, so
quote your message strings. hocon raises a parse error pointing at the offending character
rather than guessing — this is spec-correct behavior.
Bare words are convenient for identifiers, numeric values, and simple paths
(localhost, recent-files, 8080). For anything with punctuation, quote it.
String escapes
Quoted strings support the JSON escapes — \", \\, \/, \n, \t, \r, \b, \f, and
\uXXXX:
path = "C:\\Users\\Ada"
caption = "line one\nline two"
heart = "❤"
Triple-quoted strings are raw — no escapes are processed and newlines are kept verbatim, which is handy for embedded text:
banner = """
Welcome to Roamer.
Press ? for help.
"""
Path-expression keys
A key may be a dotted path, which expands into nested objects. These two documents are equivalent:
a.b.c = value
a { b { c = value } }
Keys that target the same object merge, so you can group related settings flatly:
cart.items = "{count} items"
cart.empty = "Your cart is empty"
resolves to a cart object with both items and empty.
A key is a full path expression: each element may be quoted or unquoted, and only an
unquoted . separates elements — inside a quoted segment a dot is literal. An unquoted key
with interior whitespace is a single element (its edges trimmed), so a b c is one key, not
three:
foo."bar.baz" = 1 # a two-element path; the second element is literally "bar.baz"
"a.b" = 2 # a single key containing a dot
a b c = 3 # the single key "a b c"
A key containing a literal dot is reachable through the parsed object structure but not through
the dotted-string getter API (getString("a.b")), which always splits on ..
Arrays
Array elements are separated by commas or newlines, with an optional trailing comma, and may be any value type — including objects and nested arrays:
ports = [
8080
8081
8082,
]
users = [
{ name = "Ada", admin = true },
{ name = "Alan", admin = false },
]
A document’s root may itself be an array — a file that begins with [ is parsed as a top-level
array rather than an object. Since a path-addressable Config is object-shaped, parse an
array-rooted document with Hocon.parseValue, which returns the root ConfigValue directly; see
the Config reference.
Substitutions
${path} references another value, and ${?path} is its optional form. They resolve against
the merged document, so they are order-independent:
host = localhost
url = ${host} # → "localhost"
See the substitutions guide for the full rules — environment fallback, object copying, and cycle detection.
Value concatenation
Several pieces written on one line with only whitespace between them concatenate into a single value. Strings, numbers, booleans, and substitutions join into one string, with the interior whitespace preserved and the ends trimmed:
host = example.com
port = 8080
url = "http://"${host}":"${port} # → "http://example.com:8080"
full = first middle last # → "first middle last"
Arrays concatenate element-wise, and objects deep-merge left to right:
xs = [1, 2] [3, 4] # → [1, 2, 3, 4]
conf = ${defaults} { retries = 5 } # the defaults object with retries overridden
Mixing kinds that cannot combine — an object or array joined with a string — raises a
HoconConcatException.
Appending to arrays and self-reference
A field can refer to its own previous value, which HOCON resolves by looking backward to the value already in scope rather than treating it as a cycle:
path = [/bin]
path = ${path} [/usr/bin] # → [/bin, /usr/bin]
The += shorthand appends a single element to the array already at a key (or starts a fresh
array if the key is absent) — it is exactly key = ${?key} [value]:
ports = [80]
ports += 443 # → [80, 443]
+= looks back across object blocks, so the prior value can come from an earlier
server { … } rather than the same one:
server { ports = [80] }
server { ports += 443 } # → server.ports = [80, 443]
Durations and sizes
A value can be read as a time duration or a memory size with the dedicated getters; in the source it is just a number with a unit suffix (an optional space is allowed):
timeout = 10s
poll = 500ms
linger = 5 minutes
cache = 512K
max-upload = 10MB
getDuration returns a cross-platform FiniteDuration; getBytes returns a Long. Duration
units are ns, us, ms, s, m, h, d (and their long spellings), with a bare number
read as milliseconds. Size units distinguish powers of 1024 (K, Ki, KiB, …) from powers of
1000 (kB, MB, …), with a bare number read as bytes. See the
Config reference.
Includes
An include statement pulls another document in at that point in an object. The included
fields merge as if they had been written there, so later fields override them and substitutions
see the combined tree:
include "defaults.conf"
host = override.example.com # wins over anything defaults.conf set for host
The bare form lets the source decide where to look; the qualified forms pin it to one
mechanism, and required(...) turns a missing target into an error instead of a silent skip:
include file("local.conf")
include classpath("reference.conf")
include url("https://example.com/shared.conf")
include required("must-exist.conf")
Where an include is read from goes through a ConfigSource you pass to
Hocon.parse. The default source reads files, identically on every platform (it uses the
cross-platform file API). The file(...) and required(...) qualifiers are honoured by the
default; url(...) and classpath(...) are recognised but have no portable meaning, so the
default does not serve them — supply a ConfigSource.fromMap or your own ConfigSource to
resolve those. A missing optional include is ignored; a missing required(...) one raises
IncludeException, as does a cycle of files that include each other.
Because unquoted strings forbid : and // starts a comment, URLs must be quoted
(url = "https://example.com") — this matches the reference implementation.