
Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: March 19, 2024
The HTTP protocol and APIs built on it are of central importance in programming these days.
On the JVM we have several available options, from lower-level to very high-level libraries, from established projects to new kids on the block. However, most of them are targeted primarily at Java programs.
In this article, we’re going to look at khttp, an idiomatic Kotlin library for consuming HTTP-based resources and APIs.
Before jumping into using this library, we must take into consideration the fact that it is no longer actively developed and maintained by the original author. Nonetheless, the library’s API documentation is up to date and may be used as a reference.
In order to use the library in our project, first, we have to add it to our dependencies. Even though the original artifact is no longer available, it was republished to Maven Central by the community:
<dependency>
<groupId>org.danilopianini</groupId>
<artifactId>khttp</artifactId>
<version>1.3.1</version>
</dependency>
The basics of the HTTP protocol are simple, even though the fine details can be quite complicated. Therefore, khttp has a simple interface as well.
For every HTTP method, we can find a package-level function in the khttp package, such as get, post and so on.
The functions all take the same set of arguments and return a Response object; we’ll see the details of these in the following sections.
In the course of this article, we’ll use the fully qualified form, for example, khttp.put. In our projects we can, of course, import and possibly rename those methods:
import khttp.delete as httpDelete
Note: we’ve added type declarations for clarity throughout code examples because without an IDE they could be hard to follow.
Every HTTP request has at least two required components: a method and a URL. In khttp, the method is determined by the function we invoke, as we’ve seen in the previous section.
The URL is the only required argument for the method; so, we can easily perform a simple request:
khttp.get("http://httpbin.org/get")
In the following sections, we’ll consider all requests to complete successfully.
We often have to provide query parameters in addition to the base URL, especially for GET requests.
khttp’s methods accept a params argument which is a Map of key-value pairs to include in the query String:
khttp.get(
url = "http://httpbin.org/get",
params = mapOf("key1" to "value1", "keyn" to "valuen"))
Notice that we’ve used the mapOf function to construct a Map on the fly; the resulting request URL will be:
http://httpbin.org/get?key1=value1&keyn=valuen
Another common operation we often need to perform is sending data, typically as the payload of a POST or PUT request.
For this, the library offers several options that we’re going to examine in the following sections.
We can use the json argument to send a JSON object or array. It can be of several different types:
We can easily turn our earlier GET example into a POST one which will send a simple JSON object:
khttp.post(
url = "http://httpbin.org/post",
json = mapOf("key1" to "value1", "keyn" to "valuen"))
Note that the transformation from collections to JSON objects is shallow. For example, a List of Map‘s won’t be converted to a JSON array of JSON objects, but rather to an array of strings.
For deep conversion, we’d need a more complex JSON mapping library such as Jackson. The conversion facility of the library is only meant for simple cases.
To send form data (URL encoded, as in HTML forms) we use the data argument with a Map:
khttp.post(
url = "http://httpbin.org/post",
data = mapOf("key1" to "value1", "keyn" to "valuen"))
We can send one or more files encoded as a multipart form-data request.
In that case, we use the files argument:
khttp.post(
url = "http://httpbin.org/post",
files = listOf(
FileLike("file1", "content1"),
FileLike("file2", File("kitty.jpg"))))
We can see that khttp uses a FileLike abstraction, which is an object with a name and a content. The content can be a string, a byte array, a File, or a Path.
If none of the options above are suitable, we can use an InputStream to send raw data as the body of an HTTP request:
khttp.post(url = "http://httpbin.org/post", data = someInputStream)
In this case, we’ll most likely need to manually set some headers too, which we’ll cover in a later section.
So far we’ve seen various ways of sending data to a server. But many HTTP operations are useful because of the data they return as well.
khttp is based on blocking I/O, therefore all functions corresponding to HTTP methods return a Response object containing the response received from the server.
This object has various properties that we can access, depending on the type of content.
If we know the response to be a JSON object or array, we can use the jsonObject and jsonArray properties:
val response : Response = khttp.get("http://httpbin.org/get")
val obj : JSONObject = response.jsonObject
print(obj["someProperty"])
If we want to read the response as a String instead, we can use the text property:
val message : String = response.text
Or, if we want to read it as binary data (e.g. a file download) we use the content property:
val imageData : ByteArray = response.content
Finally, we can also access the underlying InputStream:
val inputStream : InputStream = response.raw
Let’s also take a look at a couple of more advanced usage patterns that are generally useful, and that we haven’t yet treated in the previous sections.
All khttp functions take a headers argument which is a Map of header names and values.
val response = khttp.get(
url = "http://httpbin.org/get",
headers = mapOf("header1" to "1", "header2" to "2"))
Similarly for cookies:
val response = khttp.get(
url = "http://httpbin.org/get",
cookies = mapOf("cookie1" to "1", "cookie2" to "2"))
We can also access headers and cookies sent by the server in the response:
val contentType : String = response.headers["Content-Type"]
val sessionID : String = response.cookies["JSESSIONID"]
There are two types of errors that can arise in HTTP: error responses, such as 404 – Not Found, which are part of the protocol; and low-level errors, such as “connection refused”.
The first kind doesn’t result in khttp throwing exceptions; instead, we should check the Response statusCode property:
val response = khttp.get(url = "http://httpbin.org/nothing/to/see/here")
if(response.statusCode == 200) {
process(response)
} else {
handleError(response)
}
Lower-level errors, instead, result in exceptions being thrown from the underlying Java I/O subsystem, such as ConnectException.
Sometimes the server can respond with a big piece of content, and/or take a long time to respond. In those cases, we may want to process the response in chunks, rather than waiting for it to complete and take up memory.
If we want to instruct the library to give us a streaming response, then we have to pass true as the stream argument:
val response = khttp.get(url = "http://httpbin.org", stream = true)
Then, we can process it in chunks:
response.contentIterator(chunkSize = 1024).forEach { arr : ByteArray -> handleChunk(arr) }
In the unlikely case that we need to use an HTTP method (or verb) that khttp doesn’t provide natively – say, for some extension of the HTTP protocol, like WebDAV – we’re still covered.
In fact, all functions in the khttp package, which correspond to HTTP methods, are implemented using a generic request function that we can use too:
khttp.request(
method = "COPY",
url = "http://httpbin.org/get",
headers = mapOf("Destination" to "/copy-of-get"))
We haven’t touched all the features of khttp. For example, we haven’t discussed timeouts, redirects and history, or asynchronous operations.
The official documentation is the ultimate source of information about the library and all of its features.
In this tutorial, we’ve seen how to make HTTP requests in Kotlin with the idiomatic library khttp.