or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/maven-io-ktor--ktor-client-content-negotiation

Ktor client Content Negotiation support - enables automatic serialization and deserialization of request and response bodies using various formats like JSON, XML, and others

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
mavenpkg:maven/io.ktor/ktor-client-content-negotiation@3.2.x

To install, run

npx @tessl/cli install tessl/maven-io-ktor--ktor-client-content-negotiation@3.2.0

index.mddocs/

Ktor Client Content Negotiation

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.

Package Information

  • Package Name: io.ktor:ktor-client-content-negotiation
  • Package Type: maven
  • Language: Kotlin
  • Installation:
    implementation("io.ktor:ktor-client-content-negotiation:3.2.0")

Core Imports

import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.*
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.util.reflect.*
import io.ktor.utils.io.*
import io.ktor.utils.io.charsets.*

Basic Usage

import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*

// Configure client with content negotiation
val client = HttpClient {
    install(ContentNegotiation) {
        json() // Uses kotlinx.serialization JSON
    }
}

// Automatic serialization on send
data class User(val name: String, val email: String)
val user = User("Alice", "alice@example.com")
val response = client.post("https://api.example.com/users") {
    setBody(user) // Automatically serialized to JSON
}

// Automatic deserialization on receive
val createdUser: User = response.body()

Architecture

Ktor Client Content Negotiation is built around several key components:

  • ContentNegotiation Plugin: Main plugin that intercepts requests and responses
  • ContentNegotiationConfig: Configuration class for registering content converters
  • ContentConverter Interface: Defines bi-directional serialization/deserialization
  • ContentTypeMatcher Interface: Determines which converter handles which content types
  • Type Ignoring System: Mechanism to exclude certain types from content negotiation
  • Request Exclusion: Per-request control over Accept header content types

Capabilities

Plugin Installation and Configuration

Install and configure the ContentNegotiation plugin with content converters and settings.

/**
 * A plugin that serves two primary purposes:
 * - Negotiating media types between the client and server using Accept and Content-Type headers
 * - Serializing/deserializing content in specific formats when sending requests and receiving responses
 */
val ContentNegotiation: ClientPlugin<ContentNegotiationConfig>

Content Converter Registration

Register content converters for specific media types with optional configuration.

/**
 * A ContentNegotiation configuration that is used during installation
 */
class ContentNegotiationConfig : Configuration {
    /**
     * By default, Accept headers for registered content types will have no q value (implicit 1.0).
     * Set this to change that behavior for per-request basis Accept content type preferences.
     */
    var defaultAcceptHeaderQValue: Double?
    
    /**
     * Registers a contentType to a specified converter with an optional configuration script
     */
    fun <T : ContentConverter> register(
        contentType: ContentType,
        converter: T,
        configuration: T.() -> Unit = {}
    )
    
    /**
     * Registers a contentTypeToSend and contentTypeMatcher to a specified converter
     * with an optional configuration script for advanced content type handling
     */
    fun <T : ContentConverter> register(
        contentTypeToSend: ContentType,
        converter: T,
        contentTypeMatcher: ContentTypeMatcher,
        configuration: T.() -> Unit
    )
}

Usage Examples:

import io.ktor.client.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.serialization.kotlinx.xml.*

val client = HttpClient {
    install(ContentNegotiation) {
        json() // Register JSON converter
        xml() // Register XML converter
        
        // Custom q-value for Accept headers
        defaultAcceptHeaderQValue = 0.8
        
        // Register custom converter
        register(
            ContentType.Application.ProtoBuf,
            MyProtobufConverter()
        ) {
            // Configure the converter
            customOption = true
        }
    }
}

Type Ignoring Configuration

Configure which types should bypass content negotiation processing.

/**
 * Adds a type to the list of types that should be ignored by ContentNegotiation.
 * The list contains HttpStatusCode, ByteArray, String and streaming types by default.
 */
inline fun <reified T> ContentNegotiationConfig.ignoreType()

/**
 * Adds a type to the list of types that should be ignored by ContentNegotiation
 */
fun ContentNegotiationConfig.ignoreType(type: KClass<*>)

/**
 * Remove type from the list of types that should be ignored by ContentNegotiation
 */
inline fun <reified T> ContentNegotiationConfig.removeIgnoredType()

/**
 * Remove type from the list of types that should be ignored by ContentNegotiation
 */
fun ContentNegotiationConfig.removeIgnoredType(type: KClass<*>)

/**
 * Clear all configured ignored types including defaults
 */
fun ContentNegotiationConfig.clearIgnoredTypes()

Usage Examples:

val client = HttpClient {
    install(ContentNegotiation) {
        json()
        
        // Add custom types to ignore
        ignoreType<MyStreamingType>()
        ignoreType(CustomContent::class)
        
        // Remove default ignored types if needed
        removeIgnoredType<String>()
        
        // Clear all and start fresh
        clearIgnoredTypes()
    }
}

Request-Level Content Type Exclusion

Exclude specific content types from Accept headers on a per-request basis.

/**
 * Excludes the given ContentType from the list of types that will be sent in the Accept header
 * by the ContentNegotiation plugin. Can be used to not accept specific types for particular requests.
 * This can be called multiple times to exclude multiple content types.
 */
fun HttpRequestBuilder.exclude(vararg contentType: ContentType)

Usage Examples:

import io.ktor.client.request.*
import io.ktor.http.*

// Exclude JSON from this specific request
val response = client.get("https://api.example.com/data") {
    exclude(ContentType.Application.Json)
}

// Exclude multiple content types
val response2 = client.get("https://api.example.com/legacy") {
    exclude(ContentType.Application.Json, ContentType.Application.Xml)
}

JSON Content Type Matching

Advanced JSON content type matching for extended JSON formats.

/**
 * Matcher that accepts all extended json content types including application/json
 * and types ending with +json suffix
 */
object JsonContentTypeMatcher : ContentTypeMatcher {
    /**
     * Checks if contentType matches JSON patterns (application/json or */*+json)
     */
    override fun contains(contentType: ContentType): Boolean
}

Exception Handling

Handle content conversion failures and errors.

/**
 * Exception thrown when content conversion fails
 */
class ContentConverterException(message: String) : Exception(message)

/**
 * Exception thrown when no suitable converter found for deserialization
 */
class ContentConvertException(message: String) : Exception(message)

Usage Examples:

import io.ktor.client.plugins.contentnegotiation.*

try {
    val response = client.post("https://api.example.com/data") {
        setBody(complexObject)
    }
    val result: MyDataClass = response.body()
} catch (e: ContentConverterException) {
    println("Failed to convert content: ${e.message}")
    // Handle conversion error
}

Types

Core Interfaces

/**
 * A custom content converter that could be registered in ContentNegotiation plugin for any particular content type
 * Could provide bi-directional conversion implementation.
 * One of the most typical examples of content converter is a JSON content converter that provides both
 * serialization and deserialization. Implementations must override at least one method.
 */
interface ContentConverter {
    /**
     * Serializes a value to the specified contentType to a OutgoingContent.
     * This function could ignore value if it is not suitable for conversion and return null so in this case
     * other registered converters could be tried or this function could be invoked with other content types
     * it the converted has been registered multiple times with different content types.
     * 
     * @param charset response charset
     * @param typeInfo response body typeInfo  
     * @param contentType to which this data converter has been registered and that matches the client's Accept header
     * @param value to be converted
     * @return a converted OutgoingContent value, or null if value isn't suitable for this converter
     */
    suspend fun serialize(
        contentType: ContentType,
        charset: Charset,
        typeInfo: TypeInfo,
        value: Any?
    ): OutgoingContent?
    
    /**
     * Deserializes content to the value of type typeInfo
     * @return a converted value (deserialized) or null if the context's subject is not suitable for this converter
     */
    suspend fun deserialize(charset: Charset, typeInfo: TypeInfo, content: ByteReadChannel): Any?
}

/**
 * Configuration for client and server ContentNegotiation plugin
 */
interface Configuration {
    fun <T : ContentConverter> register(
        contentType: ContentType,
        converter: T,
        configuration: T.() -> Unit = {}
    )
}

/**
 * Interface for matching content types with custom logic
 */
interface ContentTypeMatcher {
    /**
     * Checks if this type matches a contentType type.
     */
    fun contains(contentType: ContentType): Boolean
}

Platform-Specific Default Ignored Types

/**
 * Common ignored types across all platforms:
 * ByteArray, String, HttpStatusCode, ByteReadChannel, OutgoingContent
 */
internal val DefaultCommonIgnoredTypes: Set<KClass<*>>

/**
 * Platform-specific ignored types (expect/actual implementation):
 * - JVM: includes InputStream
 * - JS/WASM: empty set  
 * - Native: empty set
 */
internal expect val DefaultIgnoredTypes: Set<KClass<*>>

Utility Functions

/**
 * Detect suitable charset for an application call by Accept header or fallback to defaultCharset
 */
fun Headers.suitableCharset(defaultCharset: Charset = Charsets.UTF_8): Charset

/**
 * Detect suitable charset for an application call by Accept header or fallback to null
 */
fun Headers.suitableCharsetOrNull(defaultCharset: Charset = Charsets.UTF_8): Charset?

Error Handling

The ContentNegotiation plugin can throw several types of exceptions:

  • ContentConverterException: Thrown when conversion fails during request serialization
  • ContentConvertException: Thrown when no suitable converter found during response deserialization
  • Standard HTTP exceptions: Network-related errors from the underlying HTTP client
  • Serialization exceptions: Format-specific errors from the underlying serialization libraries

Common error scenarios:

  • No converter registered for requested content type
  • Invalid data format during deserialization
  • Unsupported content type in response
  • Missing required type information
  • Type is marked as ignored but conversion was attempted
// Handle conversion errors gracefully
try {
    val result: MyData = client.get("https://api.example.com/data").body()
} catch (e: ContentConverterException) {
    // Log error and use fallback
    logger.error("Content conversion failed", e)
    // Handle error appropriately
}