{"id":237548,"date":"2022-04-11T14:31:27","date_gmt":"2022-04-11T13:31:27","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=ktor&#038;p=237548"},"modified":"2025-09-18T14:54:52","modified_gmt":"2025-09-18T13:54:52","slug":"ktor-2-0-released","status":"publish","type":"ktor","link":"https:\/\/blog.jetbrains.com\/en\/ktor\/2022\/04\/11\/ktor-2-0-released","title":{"rendered":"Ktor 2.0 Released"},"content":{"rendered":"\n<p>We\u2019re extremely excited to announce the release of Ktor 2.0. We have been working on this version for approximately one year and happy to finally get it out the door!&nbsp;<\/p>\n\n\n\n<p>Ktor 2.0 introduces many new features, but a major version which means that there are breaking changes, it has allowed us the opportunity to perform some maintenance work and get rid of legacy decisions. Don\u2019t worry though &#8211; despite the breaking changes, we\u2019ve tried our best to minimize these and also aid with some tooling that hopefully can help with automated migrations. More on that later though, first let\u2019s take a look at some of the new functionality<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Ktor Server<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Simplified Extensibility<\/h3>\n\n\n\n<p>We\u2019ve always built Ktor in mind for extensibility. Initially called Features, and now renamed to Plugins, all of the features (now you see why we renamed it) that Ktor offers are built using plugin architecture. The problem we encountered though was that for some folks it was hard to understand the architecture model behind it. In 2.0 we drastically simplified the extensibility API making the creation of plugins significantly easier.&nbsp;<\/p>\n\n\n\n<p>Take a look at the following API code from 1.x<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\">companion object Feature : ApplicationFeature&lt;ApplicationCallPipeline, CustomHeader.Configuration, CustomHeader&gt; {\n    override val key = AttributeKey&lt;CustomHeader&gt;(&quot;CustomPlugin&quot;)\n    override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -&gt; Unit): CustomHeader {\n       val configuration = Configuration().apply(configure)\n\n       val feature = CustomHeader(configuration)\n\n       pipeline.intercept(ApplicationCallPipeline.Call) {\n\n            feature.intercept(this)\n        }\n\n       return feature\n    }\n}<\/pre>\n\n\n\n<p>The equivalent in 2.0 is<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\">val myCustomPlugin = createApplicationPlugin(&quot;CustomPlugin&quot;) {\n    onCall {\n\n    }\n\n    onCallReceive {\n\n    }\n\n    onCallRespond {\n\n    }\n}<\/pre>\n\n\n\n<p>Most of the existing plugins have been converted to use the new API and we\u2019re pretty sure that the majority of scenarios can be covered. For more details, checkout the conversion for the `CustomHeader` plugin from the <a href=\"https:\/\/github.com\/ktorio\/ktor-documentation\/blob\/2.0.0\/codeSnippets\/snippets\/custom-plugin-base-api\/src\/main\/kotlin\/com\/example\/plugins\/CustomHeader.kt\" target=\"_blank\" rel=\"noopener\">old<\/a> to the <a href=\"https:\/\/github.com\/ktorio\/ktor-documentation\/blob\/2.0.0\/codeSnippets\/snippets\/custom-plugin\/src\/main\/kotlin\/com\/example\/plugins\/CustomHeaderPlugin.kt\" target=\"_blank\" rel=\"noopener\">new<\/a> API, as well as the <a href=\"https:\/\/ktor.io\/docs\/custom-plugins.html\" target=\"_blank\" rel=\"noopener\">documentation for plugin<\/a> development.<\/p>\n\n\n\n<p>We have a lot more planned when it comes to extensibility, including facilities to easily publish and consume plugins from a marketplace!&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Native support<\/h3>\n\n\n\n<p>On the server side, we now support Kotlin\/Native in addition to GraalVM (which was already supported from 1.6), meaning that you have two options when it comes to self-contained server applications.&nbsp;<\/p>\n\n\n\n<p>In regards to Kotin\/Native support, currently it is limited to using CIO as an engine and we are continuing to work in areas of performance. We do recommend however using the new Kotlin\/Native memory model.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Other server improvements<\/h3>\n\n\n\n<p>We also have a series of smaller improvements for Ktor server including random port support<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\">fun main() {\n    embeddedServer(Netty, port = 0) {\n        configureRouting()\n    }.start(wait = true)\n}<\/pre>\n\n\n\n<p>as well as improved testing API, type-safe routing, XML Serialization, subroutes for plugins, and 60+ <a href=\"https:\/\/youtrack.jetbrains.com\/issues\/KTOR?q=%23Server%20Target%20release:%202.0.0%20%23Resolved%20\" target=\"_blank\" rel=\"noopener\">bug fixes and other features<\/a>.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Ktor Client<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Simplified APIs<\/h3>\n\n\n\n<p>Continuing with the simplification of APIs, in the case of Ktor client, we\u2019ve introduced new APIs to deal with common HTTP requests<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\">val result = client.post(&quot;http:\/\/127.0.0.1:$port\/&quot;) {\n\n}\nresult.bodyAsText()<\/pre>\n\n\n\n<p>We\u2019ve gotten rid of the generic <em>post&lt;T&gt;, get&lt;T&gt;<\/em> methods. Everything now returns an `HttpResponse` where you can access the body (with `bodyAsText`, `bodyAsChannel`) as well as the headers.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Retries<\/h3>\n\n\n\n<p>We now have built-in support for retries, including adjustment of time between retries<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\">val client = HttpClient(CIO) {\n    install(HttpRequestRetry) {\n        maxRetries = 5\n        retryIf { request, response -&gt;\n            !response.status.isSuccess()\n        }\n        retryOnExceptionIf { _, cause -&gt;\n            cause is NetworkError\n        }\n        delayMillis { retry -&gt;\n            retry * 3000L\n        } \/\/ retries in 3, 6, 9, etc. seconds\n    }\n}<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Content Negotiation<\/h3>\n\n\n\n<p>If you\u2019ve been using the content negotiation feature in the server, you\u2019re probably already familiar with what it is and how it works. Essentially it\u2019s the ability for a client and server to negotiate the different types of content that can be requested and served. The negotiation aspect of it was only available up to now for the server. We\u2019ve now brought this to the client too!<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\">val client = HttpClient(CIO) {\n    install(ContentNegotiation) {\n    }\n}<\/pre>\n\n\n\n<p>This plugin effectively replaced the `JsonFeature`.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Other client improvements<\/h3>\n\n\n\n<p>In addition to the above, the client also includes shortcut APIs for authentication such as `basic()` and `bearer()` helper functions, as well as interceptors at request level, a new metrics plugin, XML serialization, and <a href=\"https:\/\/youtrack.jetbrains.com\/issues\/KTOR?q=%23Client%20%20Target%20release:%202.0.0%20%23Resolved%20\" target=\"_blank\" rel=\"noopener\">many bug fixes and other features<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Migrating to Ktor 2.0<\/h2>\n\n\n\n<p>An issue we\u2019ve had with Ktor has been the inconsistency of module and package names. Some had the prefix <em>server<\/em>, while others did not. Some worked for server and client, while not having any prefix whatsoever. In addition, we had multiple plugins in a single module, making discovery cumbersome.&nbsp;<\/p>\n\n\n\n<p>The change to a major version has provided us the opportunity to introduce some consistent naming conventions when it comes to modules and packages. Moving forward, anything that is only server, will have the <em>server <\/em>prefix. Anything that is client, will have the <em>client <\/em>prefix. Common packages will have the no<em> <\/em>prefix. In addition, plugins are now contained in their own modules.&nbsp;<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\">import io.ktor.server.application.*\nimport io.ktor.server.request.*\nimport io.ktor.server.response.*\nimport io.ktor.client.request.*\nimport io.ktor.client.statement.*\nimport io.ktor.http.content.*\nimport io.ktor.util.*\nimport io.ktor.utils.io.*<\/pre>\n\n\n\n<p>And the same goes for module names&nbsp;<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\">implementation &quot;io.ktor:ktor-server-auth:$ktor_version&quot;\nimplementation &quot;io.ktor:ktor-server-default-headers:$ktor_version&quot;\nimplementation &quot;io.ktor:ktor-server-sessions:$ktor_version&quot;\nimplementation &quot;io.ktor:ktor-server-status-pages:$ktor_version&quot;\nimplementation &quot;io.ktor:ktor-server-cors:$ktor_version&quot;\nimplementation &quot;io.ktor:ktor-server-conditional-headers:$ktor_version&quot;<\/pre>\n\n\n\n<p>To help with migration, we\u2019re providing folks not only <a href=\"https:\/\/ktor.io\/docs\/migrating-2.html\" target=\"_blank\" rel=\"noopener\">mapping documentation<\/a> but also a migration tool with <a href=\"https:\/\/ktor.io\/idea\/\" target=\"_blank\" rel=\"noopener\">IntelliJ IDEA<\/a> which attempts to migrate your Ktor project automatically <em>(please note that Kotlin Multiplatform Projects are currently not supported)<\/em>.&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/R2r5aUMoMKy3PEh7TNHzSvPZqE7ybY6hLwW-hRA7Mi71o5I6YmMDzXmggxqg2nWNsdWH1yMulHjCeo-PaPN_ouLodqzxstSkyApYKPPUHg0AXoAzCm0ldNGvTuLNdAHOd-9UhEip.png\" alt=\"\"\/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Kotlin 1.6.20 support and up to date documentation<\/h2>\n\n\n\n<p>For folks using the latest release of Kotlin, know that Ktor 2.0 is compatible with 1.6.20. We\u2019ve also continued to work on the <a href=\"https:\/\/ktor.io\/docs\/welcome.html\" target=\"_blank\" rel=\"noopener\">documentation<\/a> for Ktor, bringing it up to speed with all the changes. Moving forward we\u2019re working on revamping the API documentation also.&nbsp;<\/p>\n\n\n\n<p>For a complete list of everything in 2.0, check out the <a href=\"https:\/\/ktor.io\/changelog\/2.0\/\" target=\"_blank\" rel=\"noopener\">change log<\/a>. And <a href=\"https:\/\/ktor.io\/create\/\" target=\"_blank\" rel=\"noopener\">create<\/a> your first Ktor 2.0 today!&nbsp;<\/p>\n","protected":false},"author":1142,"featured_media":0,"comment_status":"closed","ping_status":"closed","template":"","categories":[],"tags":[],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/ktor\/237548"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/ktor"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/types\/ktor"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/users\/1142"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/comments?post=237548"}],"version-history":[{"count":8,"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/ktor\/237548\/revisions"}],"predecessor-version":[{"id":633160,"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/ktor\/237548\/revisions\/633160"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/media?parent=237548"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/categories?post=237548"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/tags?post=237548"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/cross-post-tag?post=237548"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}