Ktor client Content Negotiation support - enables automatic serialization and deserialization of request and response bodies using various formats like JSON, XML, and others
npx @tessl/cli install tessl/maven-io-ktor--ktor-client-content-negotiation@3.2.00
# Ktor Client Content Negotiation
1
2
Ktor Client Content Negotiation provides automatic serialization and deserialization of request and response bodies using various formats like JSON, XML, and protobuf. It handles media type negotiation between client and server using Accept and Content-Type headers, while offering extensible converter registration mechanisms with built-in support for popular serialization libraries.
3
4
## Package Information
5
6
- **Package Name**: io.ktor:ktor-client-content-negotiation
7
- **Package Type**: maven
8
- **Language**: Kotlin
9
- **Installation**:
10
```kotlin
11
implementation("io.ktor:ktor-client-content-negotiation:3.2.0")
12
```
13
14
## Core Imports
15
16
```kotlin
17
import io.ktor.client.plugins.contentnegotiation.*
18
import io.ktor.serialization.*
19
import io.ktor.http.*
20
import io.ktor.http.content.*
21
import io.ktor.util.reflect.*
22
import io.ktor.utils.io.*
23
import io.ktor.utils.io.charsets.*
24
```
25
26
## Basic Usage
27
28
```kotlin
29
import io.ktor.client.*
30
import io.ktor.client.call.*
31
import io.ktor.client.plugins.contentnegotiation.*
32
import io.ktor.client.request.*
33
import io.ktor.serialization.kotlinx.json.*
34
35
// Configure client with content negotiation
36
val client = HttpClient {
37
install(ContentNegotiation) {
38
json() // Uses kotlinx.serialization JSON
39
}
40
}
41
42
// Automatic serialization on send
43
data class User(val name: String, val email: String)
44
val user = User("Alice", "alice@example.com")
45
val response = client.post("https://api.example.com/users") {
46
setBody(user) // Automatically serialized to JSON
47
}
48
49
// Automatic deserialization on receive
50
val createdUser: User = response.body()
51
```
52
53
## Architecture
54
55
Ktor Client Content Negotiation is built around several key components:
56
57
- **ContentNegotiation Plugin**: Main plugin that intercepts requests and responses
58
- **ContentNegotiationConfig**: Configuration class for registering content converters
59
- **ContentConverter Interface**: Defines bi-directional serialization/deserialization
60
- **ContentTypeMatcher Interface**: Determines which converter handles which content types
61
- **Type Ignoring System**: Mechanism to exclude certain types from content negotiation
62
- **Request Exclusion**: Per-request control over Accept header content types
63
64
## Capabilities
65
66
### Plugin Installation and Configuration
67
68
Install and configure the ContentNegotiation plugin with content converters and settings.
69
70
```kotlin { .api }
71
/**
72
* A plugin that serves two primary purposes:
73
* - Negotiating media types between the client and server using Accept and Content-Type headers
74
* - Serializing/deserializing content in specific formats when sending requests and receiving responses
75
*/
76
val ContentNegotiation: ClientPlugin<ContentNegotiationConfig>
77
```
78
79
### Content Converter Registration
80
81
Register content converters for specific media types with optional configuration.
82
83
```kotlin { .api }
84
/**
85
* A ContentNegotiation configuration that is used during installation
86
*/
87
class ContentNegotiationConfig : Configuration {
88
/**
89
* By default, Accept headers for registered content types will have no q value (implicit 1.0).
90
* Set this to change that behavior for per-request basis Accept content type preferences.
91
*/
92
var defaultAcceptHeaderQValue: Double?
93
94
/**
95
* Registers a contentType to a specified converter with an optional configuration script
96
*/
97
fun <T : ContentConverter> register(
98
contentType: ContentType,
99
converter: T,
100
configuration: T.() -> Unit = {}
101
)
102
103
/**
104
* Registers a contentTypeToSend and contentTypeMatcher to a specified converter
105
* with an optional configuration script for advanced content type handling
106
*/
107
fun <T : ContentConverter> register(
108
contentTypeToSend: ContentType,
109
converter: T,
110
contentTypeMatcher: ContentTypeMatcher,
111
configuration: T.() -> Unit
112
)
113
}
114
```
115
116
**Usage Examples:**
117
118
```kotlin
119
import io.ktor.client.*
120
import io.ktor.client.plugins.contentnegotiation.*
121
import io.ktor.serialization.kotlinx.json.*
122
import io.ktor.serialization.kotlinx.xml.*
123
124
val client = HttpClient {
125
install(ContentNegotiation) {
126
json() // Register JSON converter
127
xml() // Register XML converter
128
129
// Custom q-value for Accept headers
130
defaultAcceptHeaderQValue = 0.8
131
132
// Register custom converter
133
register(
134
ContentType.Application.ProtoBuf,
135
MyProtobufConverter()
136
) {
137
// Configure the converter
138
customOption = true
139
}
140
}
141
}
142
```
143
144
### Type Ignoring Configuration
145
146
Configure which types should bypass content negotiation processing.
147
148
```kotlin { .api }
149
/**
150
* Adds a type to the list of types that should be ignored by ContentNegotiation.
151
* The list contains HttpStatusCode, ByteArray, String and streaming types by default.
152
*/
153
inline fun <reified T> ContentNegotiationConfig.ignoreType()
154
155
/**
156
* Adds a type to the list of types that should be ignored by ContentNegotiation
157
*/
158
fun ContentNegotiationConfig.ignoreType(type: KClass<*>)
159
160
/**
161
* Remove type from the list of types that should be ignored by ContentNegotiation
162
*/
163
inline fun <reified T> ContentNegotiationConfig.removeIgnoredType()
164
165
/**
166
* Remove type from the list of types that should be ignored by ContentNegotiation
167
*/
168
fun ContentNegotiationConfig.removeIgnoredType(type: KClass<*>)
169
170
/**
171
* Clear all configured ignored types including defaults
172
*/
173
fun ContentNegotiationConfig.clearIgnoredTypes()
174
```
175
176
**Usage Examples:**
177
178
```kotlin
179
val client = HttpClient {
180
install(ContentNegotiation) {
181
json()
182
183
// Add custom types to ignore
184
ignoreType<MyStreamingType>()
185
ignoreType(CustomContent::class)
186
187
// Remove default ignored types if needed
188
removeIgnoredType<String>()
189
190
// Clear all and start fresh
191
clearIgnoredTypes()
192
}
193
}
194
```
195
196
### Request-Level Content Type Exclusion
197
198
Exclude specific content types from Accept headers on a per-request basis.
199
200
```kotlin { .api }
201
/**
202
* Excludes the given ContentType from the list of types that will be sent in the Accept header
203
* by the ContentNegotiation plugin. Can be used to not accept specific types for particular requests.
204
* This can be called multiple times to exclude multiple content types.
205
*/
206
fun HttpRequestBuilder.exclude(vararg contentType: ContentType)
207
```
208
209
**Usage Examples:**
210
211
```kotlin
212
import io.ktor.client.request.*
213
import io.ktor.http.*
214
215
// Exclude JSON from this specific request
216
val response = client.get("https://api.example.com/data") {
217
exclude(ContentType.Application.Json)
218
}
219
220
// Exclude multiple content types
221
val response2 = client.get("https://api.example.com/legacy") {
222
exclude(ContentType.Application.Json, ContentType.Application.Xml)
223
}
224
```
225
226
### JSON Content Type Matching
227
228
Advanced JSON content type matching for extended JSON formats.
229
230
```kotlin { .api }
231
/**
232
* Matcher that accepts all extended json content types including application/json
233
* and types ending with +json suffix
234
*/
235
object JsonContentTypeMatcher : ContentTypeMatcher {
236
/**
237
* Checks if contentType matches JSON patterns (application/json or */*+json)
238
*/
239
override fun contains(contentType: ContentType): Boolean
240
}
241
```
242
243
### Exception Handling
244
245
Handle content conversion failures and errors.
246
247
```kotlin { .api }
248
/**
249
* Exception thrown when content conversion fails
250
*/
251
class ContentConverterException(message: String) : Exception(message)
252
253
/**
254
* Exception thrown when no suitable converter found for deserialization
255
*/
256
class ContentConvertException(message: String) : Exception(message)
257
```
258
259
**Usage Examples:**
260
261
```kotlin
262
import io.ktor.client.plugins.contentnegotiation.*
263
264
try {
265
val response = client.post("https://api.example.com/data") {
266
setBody(complexObject)
267
}
268
val result: MyDataClass = response.body()
269
} catch (e: ContentConverterException) {
270
println("Failed to convert content: ${e.message}")
271
// Handle conversion error
272
}
273
```
274
275
## Types
276
277
### Core Interfaces
278
279
```kotlin { .api }
280
/**
281
* A custom content converter that could be registered in ContentNegotiation plugin for any particular content type
282
* Could provide bi-directional conversion implementation.
283
* One of the most typical examples of content converter is a JSON content converter that provides both
284
* serialization and deserialization. Implementations must override at least one method.
285
*/
286
interface ContentConverter {
287
/**
288
* Serializes a value to the specified contentType to a OutgoingContent.
289
* This function could ignore value if it is not suitable for conversion and return null so in this case
290
* other registered converters could be tried or this function could be invoked with other content types
291
* it the converted has been registered multiple times with different content types.
292
*
293
* @param charset response charset
294
* @param typeInfo response body typeInfo
295
* @param contentType to which this data converter has been registered and that matches the client's Accept header
296
* @param value to be converted
297
* @return a converted OutgoingContent value, or null if value isn't suitable for this converter
298
*/
299
suspend fun serialize(
300
contentType: ContentType,
301
charset: Charset,
302
typeInfo: TypeInfo,
303
value: Any?
304
): OutgoingContent?
305
306
/**
307
* Deserializes content to the value of type typeInfo
308
* @return a converted value (deserialized) or null if the context's subject is not suitable for this converter
309
*/
310
suspend fun deserialize(charset: Charset, typeInfo: TypeInfo, content: ByteReadChannel): Any?
311
}
312
313
/**
314
* Configuration for client and server ContentNegotiation plugin
315
*/
316
interface Configuration {
317
fun <T : ContentConverter> register(
318
contentType: ContentType,
319
converter: T,
320
configuration: T.() -> Unit = {}
321
)
322
}
323
324
/**
325
* Interface for matching content types with custom logic
326
*/
327
interface ContentTypeMatcher {
328
/**
329
* Checks if this type matches a contentType type.
330
*/
331
fun contains(contentType: ContentType): Boolean
332
}
333
```
334
335
### Platform-Specific Default Ignored Types
336
337
```kotlin { .api }
338
/**
339
* Common ignored types across all platforms:
340
* ByteArray, String, HttpStatusCode, ByteReadChannel, OutgoingContent
341
*/
342
internal val DefaultCommonIgnoredTypes: Set<KClass<*>>
343
344
/**
345
* Platform-specific ignored types (expect/actual implementation):
346
* - JVM: includes InputStream
347
* - JS/WASM: empty set
348
* - Native: empty set
349
*/
350
internal expect val DefaultIgnoredTypes: Set<KClass<*>>
351
```
352
353
### Utility Functions
354
355
```kotlin { .api }
356
/**
357
* Detect suitable charset for an application call by Accept header or fallback to defaultCharset
358
*/
359
fun Headers.suitableCharset(defaultCharset: Charset = Charsets.UTF_8): Charset
360
361
/**
362
* Detect suitable charset for an application call by Accept header or fallback to null
363
*/
364
fun Headers.suitableCharsetOrNull(defaultCharset: Charset = Charsets.UTF_8): Charset?
365
```
366
367
## Error Handling
368
369
The ContentNegotiation plugin can throw several types of exceptions:
370
371
- **ContentConverterException**: Thrown when conversion fails during request serialization
372
- **ContentConvertException**: Thrown when no suitable converter found during response deserialization
373
- **Standard HTTP exceptions**: Network-related errors from the underlying HTTP client
374
- **Serialization exceptions**: Format-specific errors from the underlying serialization libraries
375
376
Common error scenarios:
377
- No converter registered for requested content type
378
- Invalid data format during deserialization
379
- Unsupported content type in response
380
- Missing required type information
381
- Type is marked as ignored but conversion was attempted
382
383
```kotlin
384
// Handle conversion errors gracefully
385
try {
386
val result: MyData = client.get("https://api.example.com/data").body()
387
} catch (e: ContentConverterException) {
388
// Log error and use fallback
389
logger.error("Content conversion failed", e)
390
// Handle error appropriately
391
}
392
```