skip to content

Compare Ktor's `install(ContentNegotiation) { json() }` configuration DSL with a settings-object DSL like `Json { }`. How are their builder lambdas structured, and what does the configuration lambda's receiver type tell you?

level: middleimportance: should knowfreq 40%

answer

  1. Receiver type = the menu inside the braces
  2. Json { } returns immutable config you hold
  3. install(Plugin) { } configures+registers into pipeline (side effect)
  4. Settings DSL assigns props; plugin Config exposes verbs
  5. Both are Builder.() -> Unit via trailing lambda

basics

~20 s

Both use a trailing lambda over a settings object. Json { } configures a JsonBuilder; install(Plugin) { } configures that plugin's own Configuration class. The receiver type tells you which settings are available inside the block.

solid answer

~40 s

Both follow the receiver-lambda-over-settings idiom. `Json { ... }` takes a `JsonBuilder.() -> Unit`; inside, `this` is a JsonBuilder and you set its fields. `install(plugin) { ... }` is generic: `fun install(plugin: Plugin<..., Configuration, ...>, configure: Configuration.() -> Unit)`. The lambda's receiver is the **plugin's own Configuration type**, so for ContentNegotiation the receiver exposes registration functions like `json(...)`, `register(...)`. The receiver type is the contract: whatever members the Configuration class declares are the available DSL verbs in the block. The difference is mostly intent: `Json { }` produces a standalone immutable config object you hold; `install { }` registers and configures a plugin into a pipeline as a side effect, and the configured plugin lives inside the application rather than being returned to you. Both rely on the same function-type-with-receiver mechanism.

code

kotlin · 9 lines
kotlin
// settings DSL: returns an immutable value
val json: Json = Json { prettyPrint = true; ignoreUnknownKeys = true }

// plugin DSL: receiver is ContentNegotiationConfig; side effect installs into pipeline
fun Application.module() {
    install(ContentNegotiation) {
        json(json)            // 'json' is a verb of the plugin's Configuration
    }
}

go deeper

for a junior

Recognizes both are { } config blocks but may not name the receiver types.

for a middle

Identifies the Configuration receiver as the contract and that install has a side effect vs Json returning a value.

for a senior

Explains nesting, properties-vs-verbs, and how to introspect available DSL members via the Configuration type.

for a principal

Reasons about plugin/extension API design: pipeline registration, discoverability, and composing standalone configs into plugins.

## Two flavors of the same idiom Both `Json { }` and `install(Plugin) { }` are **receiver-lambda config DSLs**. The lambda's **receiver type** determines what you can write inside the braces. ### Settings-object DSL: `Json { }` ```kotlin fun Json(builderAction: JsonBuilder.() -> Unit): Json ``` - Receiver: `JsonBuilder` — a mutable settings holder. - Inside: assign `var` properties (`isLenient`, `prettyPrint`, `ignoreUnknownKeys`). - Output: returns an **immutable `Json`** you keep and use. ### Plugin-config DSL: `install(ContentNegotiation) { }` ```kotlin fun <P, B, F> Application.install(plugin: Plugin<P, B, F>, configure: B.() -> Unit): F ``` (conceptually; `B` is the plugin's Configuration type.) - Receiver: the plugin's **Configuration** class. For ContentNegotiation that exposes converter-registration verbs like `json(...)`, `register(contentType, converter)`. - Inside: you don't just set flags — you **call registration functions** the Configuration declares. - Output/effect: the plugin is **installed into the application's pipeline** as a side effect; the call returns the plugin instance but you typically use it for its effect. ```kotlin fun Application.module() { install(ContentNegotiation) { json(Json { prettyPrint = true; ignoreUnknownKeys = true }) // nested settings DSL inside plugin DSL } } ``` Note the **nesting**: the standalone `Json { }` settings DSL is passed as an argument to `json(...)`, which is a verb of the ContentNegotiation Configuration. ## What the receiver type tells you The receiver type is the **menu** of the block. To know what is legal inside `install(X) { }`, look at `X`'s Configuration class — its public members are exactly the DSL verbs available. This is why IDE autocomplete inside the block lists the Configuration's API. ## Key differences - **Return vs side effect:** `Json { }` returns a value you hold; `install { }` mutates the application pipeline. - **Properties vs verbs:** settings DSLs mostly assign properties; plugin Configs often expose registration *functions*. - **Lifecycle:** a Json config is a plain object; an installed plugin participates in request handling. ## Same underlying mechanism Both are function types with receiver (`Builder.() -> Unit` / `Configuration.() -> Unit`) invoked via trailing-lambda syntax, with `apply`-style 'run lambda against a fresh object' internals. The difference is what happens to that object afterward. ### Key APIs/keywords - `Configuration.() -> Unit` plugin config lambda - `install(Plugin) { }` returns the plugin / configures the pipeline - `JsonBuilder.() -> Unit` settings lambda returns immutable Json - Receiver type = available DSL verbs/properties - Nesting a settings DSL inside a plugin DSL (`json(Json { })`)

  • How do you discover what functions/properties are legal inside an install { } block?
    Look at the plugin's Configuration class — its public members are exactly the DSL verbs/properties available, which is also what IDE autocomplete shows.
  • What is the main behavioral difference between Json { } and install { }?
    Json { } returns an immutable config you hold; install { } configures and registers a plugin into the application pipeline as a side effect.

Json { } is filling out a form you keep; install(Plugin) { } is configuring an appliance and bolting it into the building's wiring.

saying these in an interview costs you the question

  • Saying install(Plugin) { } returns a config object you must keep and use like Json
  • Thinking the install block can set arbitrary fields rather than the plugin's Configuration members
  • Not realizing a settings DSL can be nested inside a plugin DSL
  • Claiming the two use different language mechanisms

context