A Ktor plugin for versioning APIs using the Accept header (Media Type Versioning).
This plugin allows you to version your API routes based on the Accept header. It supports:
- Automatic validation of requested vendor media types.
- Route-based versioning using a clean DSL.
- Default versions for generic
Acceptheaders (e.g.,*/*,application/json). - Configurable vendor prefixes and generic types.
The MediaTypeVersioning plugin is route-scoped. You can install it inside your routing block:
routing {
mediaTypeVersioning(
versions = listOf(
Versions.Contract.v1,
Versions.Contract.v2
),
defaultVersion = Versions.Contract.v2,
vendorPrefix = "vnd.com.ps" // Optional, default is "vnd.com.ps"
) {
// Versioned routes go here
}
}It's recommended to define your media types in an object for consistency:
object Versions {
object Contract {
val v1 = ContentType.parse("application/vnd.com.ps.contracts.v1+json")
val v2 = ContentType.parse("application/vnd.com.ps.contracts.v2+json")
}
}Use the version selector to define version-specific logic for a route:
route("/contracts/{id}") {
version(Versions.Contract.v1) {
get {
val id = call.parameters["id"] ?: "0"
call.respond(ContractV1(id, "Contract V1"))
}
}
version(Versions.Contract.v2, isDefault = true) {
get {
val id = call.parameters["id"] ?: "0"
call.respond(ContractV2(id, "Full Contract V2"))
}
}
}The mediaTypeVersioning block accepts several parameters:
| Parameter | Type | Description |
|---|---|---|
versions |
List<ContentType> |
List of supported media types. |
defaultVersion |
ContentType? |
The version to use when a generic Accept header is provided. |
vendorPrefix |
String? |
The prefix used to identify vendor-specific media types (default: vnd.com.ps). |
genericTypes |
List<String>? |
List of types considered "generic" (default: */*, application/json). |
- Specific Version Requested: If the client sends
Accept: application/vnd.com.ps.v1+json, the correspondingversion(V1)block is executed. - Generic Request: If the client sends
Accept: application/jsonor*/*, the block marked withisDefault = true(withinversion) or thedefaultVersionconfigured in the plugin is used. - Unsupported Version: If the client sends a vendor media type that is not in the
versionslist, the plugin responds with406 Not Acceptable.
When a client sends a generic Accept header (like */* or application/json), the plugin needs to know which version
of the route to execute and what Content-Type to return.
In your routing tree, you can mark a specific version as the default using isDefault = true:
version(Versions.Contract.v2, isDefault = true) {
// This block will be executed for generic Accept headers
}If multiple routes match the path but different versions, the one with isDefault = true will be selected when the
Accept header is generic.
While isDefault = true handles routing, the defaultVersion parameter in the mediaTypeVersioning configuration
handles the response header:
mediaTypeVersioning(
versions = listOf(/* ... */),
defaultVersion = Versions.Contract.v2
) { /* ... */ }When a generic request is handled, the plugin will automatically add the Content-Type header to the response with the
value specified in defaultVersion. This ensures the client knows which version of the resource it actually received.
Here's a list of features included in this project:
| Name | Description |
|---|---|
| Routing | Provides a structured routing DSL |
| Content Negotiation | Provides automatic content conversion according to Content-Type and Accept headers |
To build or run the project, use one of the following tasks:
| Task | Description |
|---|---|
./gradlew test |
Run the tests |
./gradlew build |
Build everything |
./gradlew buildFatJar |
Build an executable JAR of the server with all dependencies included |
./gradlew buildImage |
Build the docker image to use with the fat JAR |
./gradlew publishImageToLocalRegistry |
Publish the docker image locally |
./gradlew run |
Run the server |
./gradlew runDocker |
Run using the local docker image |
If the server starts successfully, you'll see the following output:
2024-12-04 14:32:45.584 [main] INFO Application - Application started in 0.303 seconds.
2024-12-04 14:32:45.682 [main] INFO Application - Responding at http://0.0.0.0:8080