Week 38 — How can one make an HTTP request in a Java application?

Question of the Week #38
How can one make an HTTP request in a Java application?
7 Replies
Eric McIntyre
Eric McIntyre15mo ago
You can make an HTTP request in a Java application using the built-in java.net package or by using popular third-party libraries like Apache HttpClient or OkHttp.
Submission from maziogra
Eric McIntyre
Eric McIntyre15mo ago
Http Requests in Java (The OG Way) The simplest way to make an http request in Java is by using the URLConnection abstract class of the java.net package. The following example shows a GET request to some api on the localhost server:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;

//...
try (
InputStream connection = new URL(
"http://localhost:8080/api/courses/all"
).openStream();

var reader = new BufferedReader(
new InputStreamReader(connection)
)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}

} catch (IOException e) {
System.err.println("oh no my connection.. it's broken!");
System.err.println(e.getMessage());
}
//...
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;

//...
try (
InputStream connection = new URL(
"http://localhost:8080/api/courses/all"
).openStream();

var reader = new BufferedReader(
new InputStreamReader(connection)
)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}

} catch (IOException e) {
System.err.println("oh no my connection.. it's broken!");
System.err.println(e.getMessage());
}
//...
Although we didn't directly interact with the URLConnection class, the openStream() method is short for
url.openConnection().getInputStream()
url.openConnection().getInputStream()
where openConnection() returns the URLConnection instance. This stream represents the connection between the client and the web server where we are implicitly sending a GET request and getting a response back. Next, A BufferedReader is created to easily read data coming from the that response. Then we loop through each line sent by the sever with the readLine(), printing it to the console. Lastly we need to handle an IOException that could be thrown by the stream so we catch the exception in the catch clause of our try-with-resource clause. Although simple, this example is restricted only to an http GET request. You can use a subclass of URLConntion, HttpURLConnection for http specific configuration such as changing the request method or setting a request header. Here is an example using a POST request to update the api by adding a course, using the more specific httpURLConnection class.
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
//...
try {
var httpConnection = (HttpURLConnection) new URL("http://localhost:8080/api/courses").openConnection();

httpConnection.setRequestMethod("POST");
httpConnection.setRequestProperty("Content-Type", "application/json");
httpConnection.setDoOutput(true);

try (var writer = new BufferedWriter(new OutputStreamWriter(
httpConnection.getOutputStream()
))) {
String jsonCourse = """
{
"id": 2,
"students": [],
"name": "Intro to Http Requests in Java",
"startDate": "2023-09-02",
"endDate": "2023-09-03",
"subject": "PROGRAMMING"
}
""";
writer.write(jsonCourse);
}
if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
System.out.println("Request was successful");
} else {
System.out.printf("Unsuccessful request [status: %s]%n", httpConnection.getResponseCode());
}

} catch (IOException e) {
System.err.println("oh no my http connection.. it's broken!");
System.err.println(e.getMessage());
}
//...
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
//...
try {
var httpConnection = (HttpURLConnection) new URL("http://localhost:8080/api/courses").openConnection();

httpConnection.setRequestMethod("POST");
httpConnection.setRequestProperty("Content-Type", "application/json");
httpConnection.setDoOutput(true);

try (var writer = new BufferedWriter(new OutputStreamWriter(
httpConnection.getOutputStream()
))) {
String jsonCourse = """
{
"id": 2,
"students": [],
"name": "Intro to Http Requests in Java",
"startDate": "2023-09-02",
"endDate": "2023-09-03",
"subject": "PROGRAMMING"
}
""";
writer.write(jsonCourse);
}
if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
System.out.println("Request was successful");
} else {
System.out.printf("Unsuccessful request [status: %s]%n", httpConnection.getResponseCode());
}

} catch (IOException e) {
System.err.println("oh no my http connection.. it's broken!");
System.err.println(e.getMessage());
}
//...
Since, getConnection() returns an instance of HttpURLConnection as of type URLConnection we need to cast the value in order to make a POST request. We first set the request method to "POST" using the setRequestMethod() method. We then set the "Content-Type" to "application/json" as that's what your REST api endpoint consumes. We also set the DoOutput to true signaling that we want to write to the output stream. Next, we create a BufferedWriter in order to easily send the Json data as a String over to the server. Lastly, we check the status of our request to see if it equals an HTTP_OK meaning that our request was successful.
Eric McIntyre
Eric McIntyre15mo ago
Http Request as of Java 11 As of Java 11 a new and improved Http Client was created to replace HttpURLConnection. It uses the Builder Pattern to provide a higher level of abstraction with support for asynchronous requests, contrary to pre-Java 11. Here is a quick overview of the different components the new API offers: HttpClient - Used to send requests to a server using the HttpRequest. The client can choose how to send the request (asynchronous or synchronous). HttpRequest - Uses Builder methods to create a Request that an HttpClient instance will send. Here is a quick example using the course api from the previous examples:
try {
HttpRequest request = HttpRequest.newBuilder()
.GET() // also POST(), PUT(), DELETE() to specify the request method.
.uri(new URI("http://localhost:8080/api/courses/all"))
.build();
HttpClient client = HttpClient.newHttpClient();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) // BodyHandlers is used to describe how to parse the body of t
.thenApply(HttpResponse::body) // gets the body of the response
.thenAccept(System.out::println) // prints the body of the response
.join();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
try {
HttpRequest request = HttpRequest.newBuilder()
.GET() // also POST(), PUT(), DELETE() to specify the request method.
.uri(new URI("http://localhost:8080/api/courses/all"))
.build();
HttpClient client = HttpClient.newHttpClient();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) // BodyHandlers is used to describe how to parse the body of t
.thenApply(HttpResponse::body) // gets the body of the response
.thenAccept(System.out::println) // prints the body of the response
.join();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
⭐ Submission from karter907
Eric McIntyre
Eric McIntyre15mo ago
Java provides multiple ways of executing HTTP requests and obtaining responses. One of them is the class HTTPClient which has been introduced in Java 11. A HttpClient with the default configuration can be instantiated using the factory method HttpClient.newHttpClient();. Alternatively, it is possible to create a configure the HttpClient using a builder:
HttpClient httpClient = HttpClient
.newBuilder()
//HttpClient can be configured here, e.g. with
//.executor(Executors.newSingleThreadExecutor())
.build();
HttpClient httpClient = HttpClient
.newBuilder()
//HttpClient can be configured here, e.g. with
//.executor(Executors.newSingleThreadExecutor())
.build();
The same HttpClient can be used for multiple requests. For sending HTTP requests, one needs an instance of HttpRequest which can be instantiated using a builder:
HttpRequest req = HttpRequest
.newBuilder()
.uri(URI.create("https://discordjug.net"))
//specifics of the requests (e.g. request method, headers, etc.) can be configured
.build();
HttpRequest req = HttpRequest
.newBuilder()
.uri(URI.create("https://discordjug.net"))
//specifics of the requests (e.g. request method, headers, etc.) can be configured
.build();
HttpRequests can be sent using the HttpClient either synchronously using the method send or asynchronously with the method sendAsync. send returns a HttpResponse which can be used for analyzing the response while sendAsync returns a CompletableFuture of a HttpResponse which can be used for registering callbacks. Both send and sendAsync require a BodyHandler which is used for converting the response body to an object (e.g. a String, InputStream or byte[] or even custom logic (like parsing JSON)).
HttpResponse<String> res = httpClient.send(req, BodyHandlers.ofString());
int statusCode = res.statusCode();
String responseBody = res.body();//would be a different type for a different BodyHandler
//do something with the response
HttpResponse<String> res = httpClient.send(req, BodyHandlers.ofString());
int statusCode = res.statusCode();
String responseBody = res.body();//would be a different type for a different BodyHandler
//do something with the response
CompletableFuture<HttpResponse<String>> future = httpClient.sendAsync(req, BodyHandlers.ofString());
future.thenAccept(res->{
int statusCode = res.statusCode();
String responseBody = res.body();//would be a different type for a different BodyHandler
//do something with the response
}).exceptionally(e->{
//handle failure
return null;
});
CompletableFuture<HttpResponse<String>> future = httpClient.sendAsync(req, BodyHandlers.ofString());
future.thenAccept(res->{
int statusCode = res.statusCode();
String responseBody = res.body();//would be a different type for a different BodyHandler
//do something with the response
}).exceptionally(e->{
//handle failure
return null;
});
A different way of sending HTTP requests from Java which has been available in the standard libraries for longer is using HttpURLConnection. With this, it's possible to create a URL-object representing the page to request and then sending a request to that URL as well as reading the response. In case of a GET request that just requires the result body, it's possible to get an InputStream for reading the response using URl#openStream:
try(InputStream responseStream = new BufferedInputStream(new URL("https://discordjug.net").openStream())){
Files.copy(responseStream, Path.of("site.html"));//just copy the content in this example
}
try(InputStream responseStream = new BufferedInputStream(new URL("https://discordjug.net").openStream())){
Files.copy(responseStream, Path.of("site.html"));//just copy the content in this example
}
Eric McIntyre
Eric McIntyre15mo ago
It is also possible to get a Connection object from the URL using URL#openConnection. In the case of http:// or https://-URLs, this wll be a HttpURLConnection. This allows more detailed access to it.
HttpURLConnection con = (HttpURLConnection)new URL("https://discordjug.net").openConnection();
con.setRequestMethod("HEAD");//use the HEAD HTTP method which only requests headers but not the actual response

int statusCode = con.getResponseCode();//as this requires a response, it will send the request and wait
String statusMessage = con.getResponseMessage();

System.out.println(statusCode + " " + statusMessage);
System.out.println(con.getContentLength() + " bytes in the response");//should be -1 since there is no response due to the HEAD request
HttpURLConnection con = (HttpURLConnection)new URL("https://discordjug.net").openConnection();
con.setRequestMethod("HEAD");//use the HEAD HTTP method which only requests headers but not the actual response

int statusCode = con.getResponseCode();//as this requires a response, it will send the request and wait
String statusMessage = con.getResponseMessage();

System.out.println(statusCode + " " + statusMessage);
System.out.println(con.getContentLength() + " bytes in the response");//should be -1 since there is no response due to the HEAD request
⭐ Submission from dan1st
Eric McIntyre
Eric McIntyre15mo ago
There are several ways to do that, but the most modern would be using the HttpClient introduced not so long ago. It provides a clean api one can easily work with.
HttpClient client = HttpClient
.newBuilder() // Making new http client builder, which we can configure
.version(HttpClient.Version.HTTP_2) // Setting HTTP version to HTTP/2.0, can optionally also be 1.1
.connectTimeout(Duration.ofSeconds(10)) // Setting default connect timeout to 10 seconds
.followRedirects(HttpClient.Redirect.ALWAYS) // Always follow redirects
.build();
HttpRequest request = HttpRequest
.newBuilder(URI.create("https://httpbin.org/post")) // Making new http request to httpbin.org/post
.POST( // Setting the request to be a POST request, with the body being sourced from a string
HttpRequest.BodyPublishers.ofString("""
{
"hello": "world"
}
"""))
.header("Test", "Header") // Setting an example header
.timeout(Duration.ofSeconds(10)) // Setting request timeout, if the response isn't received in that time, the request fails
// Other settings are available, but mostly not relevant
.build();
// Sending the request **synchronously**, aka blocking until it arrives. Then interpreting the resulting body as String
HttpResponse<String> send = client.send(request, HttpResponse.BodyHandlers.ofString());
// Printing the response
System.out.printf("""
Received response from server: %d
Body:
%s
Headers:
%s
""",
send.statusCode(), // Received status code from the server
String.join("\n\t", send.body().split("\n")), // Body, interpreted as string (see above why)
// Since there might be several header entries with the same name, headers are represented with a Map<String, List<String>>
send.headers()
.map().entrySet()
.stream().map(pair -> pair.getKey()+": "+pair.getValue()).collect(Collectors.joining("\n\t"))
);
HttpClient client = HttpClient
.newBuilder() // Making new http client builder, which we can configure
.version(HttpClient.Version.HTTP_2) // Setting HTTP version to HTTP/2.0, can optionally also be 1.1
.connectTimeout(Duration.ofSeconds(10)) // Setting default connect timeout to 10 seconds
.followRedirects(HttpClient.Redirect.ALWAYS) // Always follow redirects
.build();
HttpRequest request = HttpRequest
.newBuilder(URI.create("https://httpbin.org/post")) // Making new http request to httpbin.org/post
.POST( // Setting the request to be a POST request, with the body being sourced from a string
HttpRequest.BodyPublishers.ofString("""
{
"hello": "world"
}
"""))
.header("Test", "Header") // Setting an example header
.timeout(Duration.ofSeconds(10)) // Setting request timeout, if the response isn't received in that time, the request fails
// Other settings are available, but mostly not relevant
.build();
// Sending the request **synchronously**, aka blocking until it arrives. Then interpreting the resulting body as String
HttpResponse<String> send = client.send(request, HttpResponse.BodyHandlers.ofString());
// Printing the response
System.out.printf("""
Received response from server: %d
Body:
%s
Headers:
%s
""",
send.statusCode(), // Received status code from the server
String.join("\n\t", send.body().split("\n")), // Body, interpreted as string (see above why)
// Since there might be several header entries with the same name, headers are represented with a Map<String, List<String>>
send.headers()
.map().entrySet()
.stream().map(pair -> pair.getKey()+": "+pair.getValue()).collect(Collectors.joining("\n\t"))
);
Asynchronous requests are also possible:
CompletableFuture<HttpResponse<String>> httpResponseCompletableFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
CompletableFuture<Void> voidCompletableFuture = httpResponseCompletableFuture.thenAccept(resp -> {
System.out.println("Received response: "+resp);
});
voidCompletableFuture.join(); // waiting for response to arrive, to not end the program when the request is sent. This line is optional, but without a non-daemon thread blocking, the jvm will exit. This prevents that, until the request arrives.
CompletableFuture<HttpResponse<String>> httpResponseCompletableFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
CompletableFuture<Void> voidCompletableFuture = httpResponseCompletableFuture.thenAccept(resp -> {
System.out.println("Received response: "+resp);
});
voidCompletableFuture.join(); // waiting for response to arrive, to not end the program when the request is sent. This line is optional, but without a non-daemon thread blocking, the jvm will exit. This prevents that, until the request arrives.
Another rather old way to do what we did above is to use the URL and HttpURLConnection mechanism:
URL url = URI.create("https://httpbin.org/post").toURL();
HttpURLConnection huc = (HttpURLConnection) url.openConnection();
huc.addRequestProperty("Test", "Header"); // Adding `Test: Header` header to the request
huc.addRequestProperty("Content-Type", "application/json");
huc.setRequestMethod("POST"); // Setting request method to POST (note that misspellings are possible!)
huc.setConnectTimeout(10_000); // Setting connect timeout to 10 seconds (10000 millis), same as .connectTimeout on HttpClient builder
huc.setReadTimeout(10_000); // Setting read timeout to 10 seconds, same as .timeout on HttpRequest builder
huc.setDoOutput(true); // Enabling outgoing data stream on this connection (so we can upload data)
try(OutputStream outputStream = huc.getOutputStream()) {
// writing data to the connection
outputStream.write("""
{
"hello": "world"
}
""".getBytes(StandardCharsets.UTF_8));
}
// getResponseCode() actually performs the request and blocks until a response arrives
int responseCode = huc.getResponseCode();
System.out.printf("""
Received response from server: %d
Body:
""",
responseCode);
try (InputStream inputStream = huc.getInputStream()) {
// Simply writing the incoming data to stdout, I am too lazy to write a whole formatter like I did with the HttpClient
inputStream.transferTo(System.out);
}
System.out.printf("Headers:\n\t%s",
// Headers here get the same treatment as with the HttpClient api, Map<String, List<String>>
huc.getHeaderFields().entrySet()
.stream().map(pair -> pair.getKey()+": "+pair.getValue()).collect(Collectors.joining("\n\t")));
URL url = URI.create("https://httpbin.org/post").toURL();
HttpURLConnection huc = (HttpURLConnection) url.openConnection();
huc.addRequestProperty("Test", "Header"); // Adding `Test: Header` header to the request
huc.addRequestProperty("Content-Type", "application/json");
huc.setRequestMethod("POST"); // Setting request method to POST (note that misspellings are possible!)
huc.setConnectTimeout(10_000); // Setting connect timeout to 10 seconds (10000 millis), same as .connectTimeout on HttpClient builder
huc.setReadTimeout(10_000); // Setting read timeout to 10 seconds, same as .timeout on HttpRequest builder
huc.setDoOutput(true); // Enabling outgoing data stream on this connection (so we can upload data)
try(OutputStream outputStream = huc.getOutputStream()) {
// writing data to the connection
outputStream.write("""
{
"hello": "world"
}
""".getBytes(StandardCharsets.UTF_8));
}
// getResponseCode() actually performs the request and blocks until a response arrives
int responseCode = huc.getResponseCode();
System.out.printf("""
Received response from server: %d
Body:
""",
responseCode);
try (InputStream inputStream = huc.getInputStream()) {
// Simply writing the incoming data to stdout, I am too lazy to write a whole formatter like I did with the HttpClient
inputStream.transferTo(System.out);
}
System.out.printf("Headers:\n\t%s",
// Headers here get the same treatment as with the HttpClient api, Map<String, List<String>>
huc.getHeaderFields().entrySet()
.stream().map(pair -> pair.getKey()+": "+pair.getValue()).collect(Collectors.joining("\n\t")));
Although the URL and HttpURLConnection apis are very old, they still work. I wouldn't use them over the HttpClient api if available, since the HttpClient api is easier to use and supports all of the functionality of HttpURLConnection, along with some features exclusive to HttpClient. There are third party http client APIs, but HttpClient will probably suffice just fine. If in doubt, just roll your own with Socket ;):
Eric McIntyre
Eric McIntyre15mo ago
try(Socket sock = new Socket()) {
// connect to httpbin.org on port 80 (http), 10 second timeout
sock.connect(new InetSocketAddress("httpbin.org", 80), 10_000);
sock.setTcpNoDelay(true); // TPC_NODELAY flag, to prevent caching
OutputStream outputStream = sock.getOutputStream();
// write standard http GET request, going to /status/500. note the extra empty line at the end, that one is required to indicate an empty body
outputStream.write("""
GET /status/500 HTTP/1.1
Host: httpbin.org
Accept: */*
User-Agent: My very own http client

""".getBytes(StandardCharsets.UTF_8));
// flush stream, deliver any cached data
outputStream.flush();
try (InputStream inputStream = sock.getInputStream()) {
// just write the response to stdout
inputStream.transferTo(System.out);
}
// note: this code wont terminate until the server closes the connection, just serves as a proof of concept
}
try(Socket sock = new Socket()) {
// connect to httpbin.org on port 80 (http), 10 second timeout
sock.connect(new InetSocketAddress("httpbin.org", 80), 10_000);
sock.setTcpNoDelay(true); // TPC_NODELAY flag, to prevent caching
OutputStream outputStream = sock.getOutputStream();
// write standard http GET request, going to /status/500. note the extra empty line at the end, that one is required to indicate an empty body
outputStream.write("""
GET /status/500 HTTP/1.1
Host: httpbin.org
Accept: */*
User-Agent: My very own http client

""".getBytes(StandardCharsets.UTF_8));
// flush stream, deliver any cached data
outputStream.flush();
try (InputStream inputStream = sock.getInputStream()) {
// just write the response to stdout
inputStream.transferTo(System.out);
}
// note: this code wont terminate until the server closes the connection, just serves as a proof of concept
}
⭐ Submission from 0x150
Want results from more Discord servers?
Add your server