A Ktor server plugin that provides HTTP response compression and request decompression capabilities with support for gzip, deflate, and identity encoding algorithms.
—
The Compression plugin provides runtime control mechanisms to selectively suppress compression or decompression on a per-request basis, giving you fine-grained control over when these operations occur.
fun ApplicationCall.suppressCompression()Prevents the current response from being compressed, regardless of plugin configuration:
routing {
get("/no-compression") {
call.suppressCompression()
call.respondText("This response will never be compressed")
}
get("/conditional") {
if (call.request.queryParameters["raw"] == "true") {
call.suppressCompression()
}
call.respond("Conditionally compressed response")
}
}fun ApplicationCall.suppressDecompression()Prevents the current request body from being decompressed, allowing access to raw compressed data:
routing {
post("/raw-upload") {
call.suppressDecompression()
// Receive compressed bytes directly
val compressedData = call.receive<ByteArray>()
// Process compressed data or decompress manually
val originalSize = call.request.headers[HttpHeaders.ContentLength]?.toInt() ?: 0
logger.info("Received $originalSize bytes of compressed data")
call.respond("Processed raw compressed upload")
}
}val ApplicationCall.isCompressionSuppressed: BooleanDetermine if response compression has been suppressed for the current call:
routing {
get("/debug-compression") {
if (call.isCompressionSuppressed) {
call.respond("Compression is suppressed")
} else {
call.respond("Compression is active")
}
}
}val ApplicationCall.isDecompressionSuppressed: BooleanDetermine if request decompression has been suppressed for the current call:
routing {
post("/upload") {
val message = if (call.isDecompressionSuppressed) {
"Processing raw compressed data"
} else {
"Processing decompressed data"
}
logger.info(message)
val data = call.receiveText()
call.respond("Processed: ${data.length} characters")
}
}Serve pre-compressed static files without double compression:
routing {
static("/assets") {
files("static")
// Ktor automatically calls suppressCompression() for pre-compressed files
// like .gz, .br variants
}
get("/precompressed/{file}") {
val filename = call.parameters["file"]
val gzFile = File("compressed/$filename.gz")
if (gzFile.exists()) {
call.suppressCompression()
call.response.header(HttpHeaders.ContentEncoding, "gzip")
call.respondFile(gzFile)
} else {
call.respondText("File not found", status = HttpStatusCode.NotFound)
}
}
}Handle binary uploads that shouldn't be decompressed:
routing {
post("/binary-upload") {
val contentType = call.request.contentType()
if (contentType?.match(ContentType.Application.OctetStream) == true) {
// Keep binary data compressed for efficient storage
call.suppressDecompression()
val compressedBytes = call.receive<ByteArray>()
// Store compressed data directly
database.storeBinary(compressedBytes)
} else {
// Normal decompression for text/JSON data
val content = call.receiveText()
processTextContent(content)
}
call.respond("Upload processed")
}
}Skip compression for small responses or specific content types:
routing {
get("/small-response") {
val data = generateSmallResponse()
if (data.length < 100) {
// Skip compression overhead for very small responses
call.suppressCompression()
}
call.respondText(data)
}
get("/image-proxy/{id}") {
val imageData = imageService.getImage(call.parameters["id"])
// Images are already compressed
call.suppressCompression()
call.respondBytes(imageData, ContentType.Image.JPEG)
}
}Disable compression for easier debugging:
routing {
get("/api/{...}") {
if (call.request.headers["X-Debug-Mode"] == "true") {
call.suppressCompression()
}
val response = apiService.processRequest(call)
call.respond(response)
}
}Apply suppression logic globally using interceptors:
install(createApplicationPlugin("CompressionControl") {
onCall { call ->
// Suppress compression for API documentation endpoints
if (call.request.uri.startsWith("/docs/")) {
call.suppressCompression()
}
// Suppress decompression for webhook endpoints that need raw data
if (call.request.uri.startsWith("/webhooks/")) {
call.suppressDecompression()
}
}
})The suppression system uses internal attribute keys:
internal val SuppressionAttribute: AttributeKey<Boolean>
internal val DecompressionSuppressionAttribute: AttributeKey<Boolean>These attributes are automatically checked by the compression plugin during request/response processing.
Suppression calls must be made before the compression/decompression logic executes:
routing {
post("/example") {
// ✅ Correct: suppress before processing
call.suppressDecompression()
val body = call.receiveText()
// ❌ Incorrect: too late to suppress compression
call.suppressCompression() // This won't work
call.respond(body)
}
}Suppression overrides all configured conditions. Even if conditions would normally allow compression/decompression, suppression takes precedence:
install(Compression) {
matchContentType(ContentType.Text.Plain) // Condition allows text compression
}
routing {
get("/text") {
call.suppressCompression() // Overrides condition - no compression occurs
call.respondText("This text won't be compressed", ContentType.Text.Plain)
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor-server-compression-jvm