Question of the Week #38
How can one make an HTTP request in a Java application?
15 messages · Page 1 of 1 (latest)
How can one make an HTTP request in a Java application?
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.
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());
}
//...
Although we didn't directly interact with the URLConnection class, the openStream()
method is short for
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());
}
//...
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.
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
HttpRequest
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);
}
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();
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();
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
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
}
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
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"))
);
Asynchronous requests are also possible: ```java
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: ```java
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 ;):