Obtaining a HttpClient

    Injecting an HTTP client

    The above example will inject a client that targets the Twitter API.

    The above Kotlin example will inject a client that targets the Twitter API using a configuration path. Note the required escaping (backslash) on "\${path.to.config}" which is required due to Kotlin string interpolation.

    The Client annotation is also a custom scope that will manage the creation of instances and ensure they are shutdown when the application shuts down.

    The value you pass to the Client annotation can be one of the following:

    • A absolute URI. Example [https://api.twitter.com/1.1](https://api.twitter.com/1.1)

    • A relative URI, in which case the server targeted will be the current server (useful for testing)

    • A service identifier. See the section on for more information on this topic.

    Another way to create an HttpClient is with the create static method of the RxHttpClient, however this approach is not recommended as you will have to ensure you manually shutdown the client and of course no dependency injection will occur for the created client.

    Performing an HTTP GET

    Generally there are two methods of interest when working with the HttpClient. The first method is called retrieve, which will execute an HTTP request and return the body in whichever type you request (by default a String) as an RxJava Flowable.

    The retrieve method accepts an object or a String URI to the endpoint you wish to request.

    The following example shows how to use retrieve to execute an HTTP GET and receive the response body as a String:

    Using retrieve

    1. String uri = UriBuilder.of("/hello/{name}")
    2. .expand(Collections.singletonMap("name", "John"))
    3. .toString();
    4. assertEquals("/hello/John", uri);
    5. String result = client.toBlocking().retrieve(uri);
    6. assertEquals(
    7. "Hello John",
    8. result
    9. );

    Using retrieve

    1. when:
    2. String uri = UriBuilder.of("/hello/{name}")
    3. .expand(Collections.singletonMap("name", "John"))
    4. .toString()
    5. then:
    6. "/hello/John" == uri
    7. when:
    8. String result = client.toBlocking().retrieve(uri)
    9. then:
    10. "Hello John" == result

    Using retrieve

    1. val uri = UriBuilder.of("/hello/{name}")
    2. .expand(Collections.singletonMap("name", "John"))
    3. .toString()
    4. uri shouldBe "/hello/John"
    5. val result = client.toBlocking().retrieve(uri)
    6. result shouldBe "Hello John"

    Note that in this example, for illustration purposes we are calling toBlocking() to return a blocking version of the client. However, in production code you should not do this and instead rely on the non-blocking nature of the Micronaut HTTP server.

    For example the following @Controller method calls another endpoint in a non-blocking manner:

    Using the HTTP client without blocking

    1. import io.micronaut.http.HttpStatus;
    2. import io.micronaut.http.MediaType;
    3. import io.micronaut.http.annotation.*;
    4. import io.micronaut.http.client.RxHttpClient;
    5. import io.micronaut.http.client.annotation.Client;
    6. import io.reactivex.Maybe;
    7. import static io.micronaut.http.HttpRequest.GET;
    8. @Get("/hello/{name}")
    9. Maybe<String> hello(String name) { (1)
    10. return httpClient.retrieve( GET("/hello/" + name) )
    11. .firstElement(); (2)
    12. }

    Using the HTTP client without blocking

    1. import io.micronaut.http.HttpStatus
    2. import io.micronaut.http.MediaType
    3. import io.micronaut.http.annotation.*
    4. import io.micronaut.http.client.RxHttpClient
    5. import io.micronaut.http.client.annotation.Client
    6. import io.reactivex.Maybe
    7. import static io.micronaut.http.HttpRequest.GET
    8. @Get("/hello/{name}")
    9. Maybe<String> hello(String name) { (1)
    10. return httpClient.retrieve( GET("/hello/" + name) )
    11. .firstElement() (2)
    12. }
    1. import io.micronaut.http.HttpStatus
    2. import io.micronaut.http.annotation.*
    3. import io.micronaut.http.client.RxHttpClient
    4. import io.micronaut.http.client.annotation.Client
    5. import io.reactivex.Maybe
    6. import io.micronaut.http.HttpRequest.GET
    7. @Get("/hello/{name}")
    8. internal fun hello(name: String): Maybe<String> { (1)
    9. return httpClient.retrieve(GET<Any>("/hello/$name"))
    10. .firstElement() (2)
    11. }

    Debugging / Tracing the HTTP Client

    To debug the requests being sent and received from the HTTP client you can enable tracing logging via your logback.xml file:

    logback.xml

    1. <logger name="io.micronaut.http.client" level="TRACE"/>

    Client Specific Debugging / Tracing

    To enable client-specific logging you could configure the default logger for all HTTP clients. And, you could also configure different loggers for different clients using Client Specific Configuration. For example, in application.yml:

    application.yml

    1. micronaut:
    2. http:
    3. client:
    4. logger-name: mylogger
    5. services:
    6. otherClient:
    7. logger-name: other.client

    And, then enable logging in logback.yml:

    logback.xml

    Customizing the HTTP Request

    The previous example demonstrated using the static methods of the HttpRequest interface to construct a instance. Like the name suggests a MutableHttpRequest can be mutated including the ability to add headers, customize the request body and so on. For example:

    Passing an HttpRequest to retrieve

    1. Flowable<String> response = client.retrieve(
    2. GET("/hello/John")
    3. .header("X-My-Header", "SomeValue")
    4. );

    Passing an HttpRequest to retrieve

    1. Flowable<String> response = client.retrieve(
    2. GET("/hello/John")
    3. .header("X-My-Header", "SomeValue")
    4. )

    Passing an HttpRequest to retrieve

    1. val response = client.retrieve(
    2. GET<Any>("/hello/John")
    3. .header("X-My-Header", "SomeValue")
    4. )

    The above example adds an additional header called X-My-Header to the request before it is sent. The MutableHttpRequest interface has a bunch more convenience methods that make it easy to modify the request in common ways.

    Reading JSON Responses

    Typically with Microservices a message encoding format is used such as JSON. Micronaut’s HTTP client leverages Jackson for JSON parsing hence whatever type Jackson can decode can passed as a second argument to retrieve.

    For example consider the following @Controller method that returns a JSON response:

    Returning JSON from a controller

    1. @Get("/greet/{name}")
    2. Message greet(String name) {
    3. return new Message("Hello " + name);
    4. }

    Returning JSON from a controller

    1. @Get("/greet/{name}")
    2. Message greet(String name) {
    3. return new Message("Hello " + name)
    4. }

    Returning JSON from a controller

    1. @Get("/greet/{name}")
    2. internal fun greet(name: String): Message {
    3. return Message("Hello $name")
    4. }

    The method above returns a POJO of type Message which looks like:

    Message POJO

    1. import com.fasterxml.jackson.annotation.JsonCreator;
    2. import com.fasterxml.jackson.annotation.JsonProperty;
    3. public class Message {
    4. private final String text;
    5. @JsonCreator
    6. public Message(@JsonProperty("text") String text) {
    7. this.text = text;
    8. }
    9. public String getText() {
    10. return text;
    11. }
    12. }

    Message POJO

    1. import com.fasterxml.jackson.annotation.JsonCreator
    2. import com.fasterxml.jackson.annotation.JsonProperty
    3. class Message {
    4. private final String text
    5. @JsonCreator
    6. Message(@JsonProperty("text") String text) {
    7. this.text = text
    8. }
    9. String getText() {
    10. return text
    1. import com.fasterxml.jackson.annotation.JsonCreator
    2. import com.fasterxml.jackson.annotation.JsonProperty
    3. class Message @JsonCreator
    4. constructor(@param:JsonProperty("text") val text: String)

    On the client end you can call this endpoint and decode the JSON into a map using the retrieve method as follows:

    Decoding the response body to a Map

    Decoding the response body to a Map

    1. Flowable<Map> response = client.retrieve(
    2. GET("/greet/John"), Map.class
    3. )

    Decoding the response body to a Map

    1. var response: Flowable<Map<*, *>> = client.retrieve(
    2. GET<Any>("/greet/John"), Map::class.java
    3. )

    The above examples decodes the response into a Map, representing the JSON. If you wish to customize the type of the key and string you can use the Argument.of(..) method:

    Decoding the response body to a Map

    1. response = client.retrieve(
    2. GET("/greet/John"),
    3. Argument.of(Map.class, String.class, String.class) (1)
    4. );

    Decoding the response body to a Map

    1. response = client.retrieve(
    2. GET("/greet/John"),
    3. Argument.of(Map.class, String.class, String.class) (1)
    4. )

    Decoding the response body to a Map

    1. response = client.retrieve(
    2. GET<Any>("/greet/John"),
    3. Argument.of(Map::class.java, String::class.java, String::class.java) (1)
    4. )

    Whilst retrieving JSON as a map can be desirable, more often than not you will want to decode objects into Plain Old Java Objects (POJOs). To do that simply pass the type instead:

    Decoding the response body to a POJO

    1. Flowable<Message> response = client.retrieve(
    2. GET("/greet/John"), Message.class
    3. );
    4. assertEquals(
    5. "Hello John",
    6. response.blockingFirst().getText()
    7. );

    Decoding the response body to a POJO

    1. when:
    2. Flowable<Message> response = client.retrieve(
    3. GET("/greet/John"), Message.class
    4. )
    5. then:
    6. "Hello John" == response.blockingFirst().getText()

    Decoding the response body to a POJO

    1. val response = client.retrieve(
    2. GET<Any>("/greet/John"), Message::class.java
    3. )
    4. response.blockingFirst().text shouldBe "Hello John"

    Note how you can use the same Java type on both the client and the server. The implication of this is that typically you will want to define a common API project where you define the interfaces and types that define your API.

    Decoding Other Content Types

    If the server you are communicating with uses a custom content type that is not JSON by default Micronaut’s HTTP client will not know how to decode this type.

    To resolve this issue you can register MediaTypeCodec as a bean and it will be automatically picked up and used to decode (or encode) messages.

    Receiving the Full HTTP Response

    Sometimes, receiving just the object is not enough and you need information about the response. In this case, instead of retrieve you should use the exchange method:

    Recieving the Full HTTP Response

    1. Flowable<HttpResponse<Message>> call = client.exchange(
    2. GET("/greet/John"), Message.class (1)
    3. );
    4. HttpResponse<Message> response = call.blockingFirst();
    5. Optional<Message> message = response.getBody(Message.class); (2)
    6. // check the status
    7. assertEquals(
    8. HttpStatus.OK,
    9. response.getStatus() (3)
    10. );
    11. // check the body
    12. assertTrue(message.isPresent());
    13. assertEquals(
    14. "Hello John",
    15. message.get().getText()
    16. );

    Recieving the Full HTTP Response

    Recieving the Full HTTP Response

    1. val call = client.exchange(
    2. GET<Any>("/greet/John"), Message::class.java (1)
    3. )
    4. val response = call.blockingFirst()
    5. val message = response.getBody(Message::class.java) (2)
    6. // check the status
    7. response.status shouldBe HttpStatus.OK (3)
    8. // check the body
    9. message.isPresent shouldBe true