How do I use JSON libraries like Jackson or Gson in modern Java?

In modern Java, using JSON libraries like Jackson or Gson is the standard way to handle data exchange, as the Java Standard Library does not include a built-in JSON parser.

With Java 25, you can take advantage of Records for clean data models and Text Blocks for readable JSON strings in your code.

1. Using Jackson (Industry Standard)

Jackson is highly recommended for modern applications, especially within the Spring and Jakarta EE ecosystems.

Maven Dependency:

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

Code Example:

import com.fasterxml.jackson.databind.ObjectMapper;

// 1. Define a Record (Modern Java way)
public record User(Long id, String name) {}

public class JacksonDemo {
    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();

        // Serialization (Object to JSON)
        User user = new User(1L, "Duke");
        String json = mapper.writeValueAsString(user);
        System.out.println("JSON: " + json);

        // Deserialization (JSON to Object)
        String inputJson = """
                {
                    "id": 2,
                    "name": "Java"
                }
                """;
        User decodedUser = mapper.readValue(inputJson, User.class);
        System.out.println("User Name: " + decodedUser.name());
    }
}

2. Using Gson (Google’s Library)

Gson is known for its simplicity and ease of use for smaller projects or quick utilities.

Maven Dependency:

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.11.0</version>
</dependency>

Code Example:

import com.google.code.gson.Gson;
import com.google.code.gson.GsonBuilder;

public class GsonDemo {
    public static void main(String[] args) {
        // Use GsonBuilder for custom configurations like Pretty Printing
        Gson gson = new GsonBuilder().setPrettyPrinting().create();

        // Serialization
        User user = new User(10L, "Modern Java");
        String json = gson.toJson(user);
        System.out.println(json);

        // Deserialization
        User fromJson = gson.fromJson(json, User.class);
        System.out.println("ID from JSON: " + fromJson.id());
    }
}

Key Comparisons & Tips

  • Records Support: Both Jackson (2.12+) and Gson (2.10+) support Java Records natively. This is the preferred way to create POJOs for data mapping.
  • Immutability: Since Records are immutable, these libraries use reflection or canonical constructors to populate the data, which is much safer than traditional setter-based beans.
  • Performance: Jackson is generally faster and offers more features for complex mapping (like @JsonProperty for renaming fields).
  • Integration: If you are using Spring Boot, Jackson is already included and configured for you.

Integrating with HttpClient

When fetching data from an API using the java.net.http.HttpClient, you typically receive a String which you then pass to these libraries:

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
User user = mapper.readValue(response.body(), User.class);

For more advanced use cases, you can even write a custom BodyHandler that uses Jackson to parse the stream directly, saving memory on large JSON payloads.

How do I fetch JSON using java.net.http.HttpRequest?

To fetch JSON using the Java HTTP Client (java.net.http), you typically send a GET request and process the response body as a String. Since the standard library doesn’t include a JSON parser, you’ll receive the raw JSON string, which you can then parse using a library like Jackson or Gson.

Here is a clean example of how to perform the fetch:

1. Basic JSON Fetch (Java 11+)

This example demonstrates the core steps: creating the client, building the request, and receiving the response.

package org.kodejava.httpclient;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.io.IOException;

public class FetchJsonExample {
    public static void main(String[] args) {
        // 1. Create an HttpClient (try-with-resources available in Java 21+)
        try (HttpClient client = HttpClient.newHttpClient()) {

            // 2. Build the HttpRequest
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))
                    .header("Accept", "application/json") // Good practice to request JSON
                    .GET()
                    .build();

            try {
                // 3. Send the request and handle the body as a String
                HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

                // 4. Check the status code and print the JSON body
                if (response.statusCode() == 200) {
                    System.out.println("JSON Received:");
                    System.out.println(response.body());
                } else {
                    System.err.println("Error: " + response.statusCode());
                }
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2. Fetching Asynchronously

If you don’t want to block the main thread while waiting for the network, use sendAsync. This returns a CompletableFuture:

client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
      .thenApply(HttpResponse::body)
      .thenAccept(System.out::println)
      .join(); // Wait for completion (for demo purposes)

Key Tips for JSON fetching:

  • Headers: Always include .header("Accept", "application/json") so the server knows you expect a JSON response.
  • Parsing: To turn that String into a Java Object, you would typically use a library. For example, with Jackson:
    ObjectMapper mapper = new ObjectMapper();
        MyObject obj = mapper.readValue(response.body(), MyObject.class);
    
  • Timeouts: It’s a “friendly” habit to set a timeout so your application doesn’t hang indefinitely:
    HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .timeout(java.time.Duration.ofSeconds(10))
                .build();
    

How do I use WebSocket with HttpClient?

To use WebSockets with the Java native HttpClient (introduced in Java 11 and fully supported in Java 25), you use the WebSocket.Builder.

The process involves three main steps:
1. Create an HttpClient (or use an existing one).
2. Implement a WebSocket.Listener to handle incoming messages and events.
3. Build and open the connection using HttpClient.newWebSocketBuilder().

Here is a complete example of a simple WebSocket client:

package org.kodejava.httpclient;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.WebSocket;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

public class WebSocketExample {

    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        // 1. Build the WebSocket connection
        CompletableFuture<WebSocket> wsFuture = client.newWebSocketBuilder()
                .buildAsync(URI.create("wss://echo.websocket.org"), new EchoListener());

        // 2. Use the WebSocket instance to send data
        wsFuture.thenAccept(webSocket -> {
            System.out.println("Connected!");
            webSocket.sendText("Hello, WebSocket!", true);

            // Keep the connection open for a bit to receive the echo
            try { Thread.sleep(2000); } catch (InterruptedException e) { }

            webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "Done");
        }).join();
    }

    // 3. Implement the Listener to handle events
    private static class EchoListener implements WebSocket.Listener {

        @Override
        public void onOpen(WebSocket webSocket) {
            System.out.println("WebSocket opened");
            WebSocket.Listener.super.onOpen(webSocket);
        }

        @Override
        public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
            System.out.println("Received message: " + data);
            // Request the next message
            webSocket.request(1);
            return null; // Returning null means we're done with this message
        }

        @Override
        public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) {
            System.out.println("Closed: " + statusCode + " " + reason);
            return null;
        }

        @Override
        public void onError(WebSocket webSocket, Throwable error) {
            System.err.println("Error occurred: " + error.getMessage());
        }
    }
}

Key Concepts:

  • buildAsync(URI, Listener): This starts the opening handshake. It returns a CompletableFuture<WebSocket> because the handshake is asynchronous [1].
  • Backpressure (request(n)): By default, the client doesn’t automatically pull all messages from the server. In onText or onBinary, you usually call webSocket.request(1) to tell the client you are ready to receive the next message [2].
  • The last parameter: If a message is very large, it might be delivered in chunks. The last boolean flag tells you if the current chunk is the end of the message.
  • CompletionStage<?>: Listener methods return a CompletionStage. If you return a CompletableFuture, the client will wait for it to complete before calling that listener method again, which is useful for processing messages asynchronously without blocking the network thread [4].

How do I use HttpClient with JSON parsing?

To use HttpClient with JSON parsing in Java, the most common approach is to combine the standard java.net.http.HttpClient with a JSON library like Jackson or Gson.

While HttpClient doesn’t have a built-in JSON parser, you can map the response body string to a Java object.

Using Jackson (Recommended)

Jackson is a powerful and widely-used library in the Java ecosystem. Here is how you can fetch a JSON response and parse it into a POJO (Plain Old Java Object).

First, define your data model:

public record Post(int userId, int id, String title, String body) {}

Then, use the HttpClient to fetch the data and ObjectMapper to parse it:

package org.kodejava.httpclient;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class HttpClientJsonExample {
    public static void main(String[] args) {
        HttpClient client = HttpClient.newHttpClient();
        ObjectMapper mapper = new ObjectMapper();

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

        try {
            // Send request and get response as a String
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

            if (response.statusCode() == 200) {
                // Parse the JSON string into the Post object
                Post post = mapper.readValue(response.body(), Post.class);

                System.out.println("Post Title: " + post.title());
                System.out.println("Post Body: " + post.body());
            } else {
                System.err.println("Error: " + response.statusCode());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Tip: Custom BodyHandler

If you find yourself parsing JSON frequently, you can implement a custom HttpResponse.BodyHandler to handle the conversion automatically.

public static <T> HttpResponse.BodyHandler<T> asJson(Class<T> targetType) {
    return responseInfo -> HttpResponse.BodySubscribers.mapping(
            HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8),
            body -> {
                try {
                    return new ObjectMapper().readValue(body, targetType);
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            });
}

// Usage:
// HttpResponse<Post> response = client.send(request, asJson(Post.class));
// Post post = response.body();

Dependencies

Ensure you have the Jackson dependency in your pom.xml:

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

This approach keeps your networking logic clean while leveraging the robustness of proven JSON libraries

How do I send async HTTP requests with HttpClient?

To send asynchronous HTTP requests in Java using the java.net.http.HttpClient, you use the sendAsync() method. This method returns a CompletableFuture<HttpResponse<T>>, allowing you to handle the response without blocking the main thread.

Here is a step-by-step example of how to implement this:

1. Basic Asynchronous GET Request

This example demonstrates how to fire a request and handle the result using thenAccept.

package org.kodejava.httpclient;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;

public class AsyncRequestExample {
    public static void main(String[] args) {
        // 1. Create the HttpClient
        HttpClient client = HttpClient.newHttpClient();

        // 2. Build the HttpRequest
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))
                .GET()
                .build();

        // 3. Send the request asynchronously
        CompletableFuture<HttpResponse<String>> responseFuture =
                client.sendAsync(request, HttpResponse.BodyHandlers.ofString());

        // 4. Handle the response when it arrives
        responseFuture.thenAccept(response -> {
            System.out.println("Status Code: " + response.statusCode());
            System.out.println("Response Body: " + response.body());
        }).exceptionally(ex -> {
            System.err.println("Error occurred: " + ex.getMessage());
            return null;
        });

        // The program continues here immediately while the request is in flight
        System.out.println("Request sent! Doing other things...");

        // Optional: Block if you need to wait for the result before the program exits
        responseFuture.join();
    }
}

2. Chaining and Transforming Results

Because sendAsync returns a CompletableFuture, you can chain operations like extracting the body or converting JSON.

CompletableFuture<String> bodyFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
        .thenApply(HttpResponse::body)       // Transform response to just the body
        .thenApply(String::toUpperCase);      // Further transform the string

bodyFuture.thenAccept(System.out::println);

Key Components

  • sendAsync(request, bodyHandler): The non-blocking counterpart to send().
  • HttpResponse.BodyHandlers: Defines how to handle the incoming data (e.g., ofString(), ofByteArray(), or ofFile()).
  • CompletableFuture: Provides methods like .thenApply() (map), .thenAccept() (consume), and .exceptionally() (error handling).

Best Practices

  • Reuse the Client: Don’t create a new HttpClient for every request. It’s designed to be long-lived and shared.
  • Executor Service: By default, HttpClient uses a default executor. For high-load applications, you can provide your own thread pool when building the client:
    HttpClient client = HttpClient.newBuilder()
                .executor(Executors.newFixedThreadPool(10))
                .build();
    
  • Join/Get: In a console application, use .join() or .get() at the very end to prevent the main method from finishing (and the JVM exiting) before the background thread completes.