20

I'm new to the Java 11 HttpClient and would like to give it a try. I have a simple GET request that return JSON and I would like to map the JSON response to a Java class called Questionnaire.

I understand that I can turn the response out of box into a String or an input stream like this

HttpRequest request = HttpRequest.newBuilder(new URI(String.format("%s%s", this.baseURI, "/state")))
          .header(ACCEPT, APPLICATION_JSON)
          .PUT(noBody()).build();

HttpResponse<String> response = this.client.send(request, HttpResponse.BodyHandlers.ofString());

How can I write something that converts the JSON string to my Questionnaire class like this?

HttpResponse<Questionnaire> response = this.client.send(request, HttpResponse.BodyHandlers./* what can I do here? */);

I use Jackson to transform JSON into Java class instances. Is there Jackson support for the new Java standard HttpClient yet?

UPDATE 1 I was not precise enough, sorry about that. I am looking for a blocking get example. I was aware of http://openjdk.java.net/groups/net/httpclient/recipes.html#jsonGet

1
  • The send call of HttpClient is itself blocking if necessary to get the response. You can simply use response.body() and map it your reference model. Commented Aug 23, 2019 at 13:45

3 Answers 3

18

Solution for Java 11 HttpClient::sendAsync only

Based on this link you can do something like this :

public static void main(String[] args) throws IOException, URISyntaxException, ExecutionException, InterruptedException {
        UncheckedObjectMapper uncheckedObjectMapper = new UncheckedObjectMapper();

        HttpRequest request = HttpRequest.newBuilder(new URI("https://jsonplaceholder.typicode.com/todos/1"))
                .header("Accept", "application/json")
                .build();

        Model model = HttpClient.newHttpClient()
                .sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::body)
                .thenApply(uncheckedObjectMapper::readValue)
                .get();

        System.out.println(model);

}

class UncheckedObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper {
        /**
         * Parses the given JSON string into a Map.
         */
        Model readValue(String content) {
            try {
                return this.readValue(content, new TypeReference<Model>() {
                });
            } catch (IOException ioe) {
                throw new CompletionException(ioe);
            }
        }

}

class Model {
        private String userId;
        private String id;
        private String title;
        private boolean completed;


    //getters setters constructors toString
}

I used some dummy endpoint which provides sample JSON input and sample model class to map the response directly to Model class using Jackson.

Solution for Java 11 HttpClient::send and HttpClient::sendAsync

I found a way by defining custom HttpResponse.BodyHandler :

public class JsonBodyHandler<W> implements HttpResponse.BodyHandler<W> {

    private Class<W> wClass;

    public JsonBodyHandler(Class<W> wClass) {
        this.wClass = wClass;
    }

    @Override
    public HttpResponse.BodySubscriber<W> apply(HttpResponse.ResponseInfo responseInfo) {
        return asJSON(wClass);
    }

    public static <T> HttpResponse.BodySubscriber<T> asJSON(Class<T> targetType) {
        HttpResponse.BodySubscriber<String> upstream = HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8);

        return HttpResponse.BodySubscribers.mapping(
                upstream,
                (String body) -> {
                    try {
                        ObjectMapper objectMapper = new ObjectMapper();
                        return objectMapper.readValue(body, targetType);
                    } catch (IOException e) {
                        throw new UncheckedIOException(e);
                    }
                });
    }
}

Then I call it :

public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException {

    HttpRequest request = HttpRequest.newBuilder(new URI("https://jsonplaceholder.typicode.com/todos/1"))
                .header("Accept", "application/json")
                .build();

    Model model = HttpClient.newHttpClient()
                .send(request, new JsonBodyHandler<>(Model.class))
                .body();

    System.out.println(model);

}

The response is :

Model{userId='1', id='1', title='delectus aut autem', completed=false}

The JavaDoc of HttpResponse.BodySubscribers::mapping was particulary useful to solve this. It can be further improved to use HttpResponse.BodySubscribers::ofInputStream instead of HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8) to define the BodySubscriber for the JsonBodyHandler.

Sign up to request clarification or add additional context in comments.

4 Comments

yeah, but that is async
since CompletableFuture::get is a blocking operation it will wait anyway. I do not know if it is possible to do something like this using BodyHandler only with send.
Awesome. You helped me a lot. Thank you
You should not be creating a new instance of an ObjectMapper for every response. It is noticeably expensive to be creating a new one every time.
9

Simplifying @michalk solution for Java 11 HttpClient::send

HttpService Class Example:

public class HttpService {

private final HttpClient httpClient= HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build();

public HttpService() {}

public <T> T sendGetRequest(String url, Class<T> responseType) throws IOException, InterruptedException {
    HttpRequest request = HttpRequest.newBuilder().GET().uri(URI.create(url)).header("Accept", "application/json").build();

    HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

    return new ObjectMapper().readValue(response.body(), responseType);
}

public <T> List<T> sendGetListRequest(String url, Class<T> responseType) throws IOException, InterruptedException {

    HttpRequest request = HttpRequest.newBuilder().GET().uri(URI.create(url)).header("Accept", "application/json").build();

    HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

    ObjectMapper objectMapper = new ObjectMapper();
    return objectMapper.readValue(response.body(), objectMapper.getTypeFactory().constructCollectionType(List.class, responseType));
}}

Model Class Example:

public class Model {

private String id;

public Model() {}

public String getId() { return this.id; }

public void setId(String id) { this.id = id; }

@Override
public String toString() { return "Model{" + "id='" + id + '\'' + '}'; }}

Sending HTTP GET request:

public class Main {

public static void main(String[] args) {
    try {
        HttpService httpService = new HttpService();

        Model model = httpService.sendGetRequest("http://localhost:8080/api/v1/models/1", Model.class);
        System.out.println("Single Object:" + model);

        System.out.print('\n');

        List<Model> models = httpService.sendGetListRequest("http://localhost:8080/api/v1/models", Model.class);
        for(Model m: models) { System.out.println("Object:" + m); }

    }
    catch (IOException | InterruptedException e) {
        System.err.println("Failed to send GET request: " + e.getMessage());
    }
}}

Response:

Single Object: Model{id='1'}

Object: Model{id='1'}
Object: Model{id='2'}
Object: Model{id='3'}

Required Maven Dependency (pom.xml):

<dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.10.3</version>
    </dependency>

Comments

2

If you're OK with including a dependency, check out Methanol (disclaimer: I'm the library's author). The library has special BodyHandler implementations for object mapping. You can add JSON support by installing the jackson adapter.

var request = MutableRequest.GET("https://example.com")
    .header("Accept", "application/json");

var modelResponse = client.send(request, MoreBodyHandlers.ofObject(Model.class));

// Use TypeRef<T> for complex types
var modelListResponse = client.send(request, MoreBodyHandlers.ofObject(new TypeRef<List<Model>>() {}));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.