100

In Java, this code throws an exception when the HTTP result is 404 range:

URL url = new URL("http://stackoverflow.com/asdf404notfound");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.getInputStream(); // throws!

In my case, I happen to know that the content is 404, but I'd still like to read the body of the response anyway.

(In my actual case the response code is 403, but the body of the response explains the reason for rejection, and I'd like to display that to the user.)

How can I access the response body?

Jonik
  • 80,077
  • 70
  • 264
  • 372
Dan Fabulich
  • 37,506
  • 41
  • 139
  • 175
  • Are you sure the server is sending a body? – Hank Gay Mar 05 '09 at 01:50
  • 2
    @jdigital: the exception thrown by HttpURLConnection.getInputStream() is java.io.FileNotFoundException. (Mainly mentioning this for better googlability.) – Jonik Jan 29 '14 at 16:34

8 Answers8

186

Here is the bug report (close, will not fix, not a bug).

Their advice there is to code like this:

HttpURLConnection httpConn = (HttpURLConnection)_urlConnection;
InputStream _is;
if (httpConn.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST) {
    _is = httpConn.getInputStream();
} else {
     /* error from server */
    _is = httpConn.getErrorStream();
}
Morty
  • 746
  • 5
  • 12
TofuBeer
  • 60,850
  • 18
  • 118
  • 163
  • Does indeed seem "as expected" given the API design, sounds reasonable to me – matt b Mar 05 '09 at 03:34
  • 6
    Wouldn't you want to get the error stream when the response code is >= 400, rather than the other way around? – Stephen Swensen Apr 13 '10 at 16:14
  • 3
    In case of errors, getInputStream() will throw an IO Exception. You should catch the exception and read from error stream using getErrorStream(). This seems to be a better approach than checking on httpresponse code. – Sudarshan Bhat Aug 20 '12 at 12:04
  • 4
    The problem is that if you read the HttpUrlConnection.getErrorStream() code, you'll see that it ALWAYS returns null. (Java 6) :-( – Gangnus Mar 25 '14 at 13:28
  • 6
    Won't other success codes like "201 CREATED" fail here? – Rich Oct 20 '14 at 13:01
  • 1
    Yes Rich, the code is poorly written, but it is what they provided. The idea is right, the implementation leaves a lot to be desired. – TofuBeer Oct 20 '14 at 18:17
  • 4
    The bug report suggest checking on `httpConn.getResponseCode() >= 400` (and their work-around has an error, flipping the inputstreams to use) – Dag Aug 08 '15 at 23:25
  • To be clear... the example provided here is specifically referring to a soap client implementation. – Maybe_Factor Aug 07 '17 at 04:56
  • 1
    This doesn't seem right to me. The implementation of `HttpURLConnection#getResponseCode()` calls `HttpURLConnection#getInputStream()` if the response code is not already present. So trying to see what the response code is to judge which stream you want will result in the same error. This seems wrong. – searchengine27 Jan 02 '19 at 20:04
  • 1
    @searchengine27 `getResponseCode()` only calls `getInputStream()` to ensure the connection is there. It catches and stores the exception before checking the header 0 position which is for status code. It can still throw here but it will return the status code without exception if exception is thrown on `getInputStream()` and not an issue with getting status code itself. – doubleA Sep 18 '19 at 19:50
17

It's the same problem I was having: HttpUrlConnection returns FileNotFoundException if you try to read the getInputStream() from the connection.
You should instead use getErrorStream() when the status code is higher than 400.

More than this, please be careful since it's not only 200 to be the success status code, even 201, 204, etc. are often used as success statuses.

Here is an example of how I went to manage it

... connection code code code ...

// Get the response code 
int statusCode = connection.getResponseCode();

InputStream is = null;

if (statusCode >= 200 && statusCode < 400) {
   // Create an InputStream in order to extract the response object
   is = connection.getInputStream();
}
else {
   is = connection.getErrorStream();
}

... callback/response to your handler....

In this way, you'll be able to get the needed response in both success and error cases.

Hope this helps!

Cleb
  • 25,102
  • 20
  • 116
  • 151
gpiazzese
  • 445
  • 7
  • 9
14

In .Net you have the Response property of the WebException that gives access to the stream ON an exception. So i guess this is a good way for Java,...

private InputStream dispatch(HttpURLConnection http) throws Exception {
    try {
        return http.getInputStream();
    } catch(Exception ex) {
        return http.getErrorStream();
    }
}

Or an implementation i used. (Might need changes for encoding or other things. Works in current environment.)

private String dispatch(HttpURLConnection http) throws Exception {
    try {
        return readStream(http.getInputStream());
    } catch(Exception ex) {
        readAndThrowError(http);
        return null; // <- never gets here, previous statement throws an error
    }
}

private void readAndThrowError(HttpURLConnection http) throws Exception {
    if (http.getContentLengthLong() > 0 && http.getContentType().contains("application/json")) {
        String json = this.readStream(http.getErrorStream());
        Object oson = this.mapper.readValue(json, Object.class);
        json = this.mapper.writer().withDefaultPrettyPrinter().writeValueAsString(oson);
        throw new IllegalStateException(http.getResponseCode() + " " + http.getResponseMessage() + "\n" + json);
    } else {
        throw new IllegalStateException(http.getResponseCode() + " " + http.getResponseMessage());
    }
}

private String readStream(InputStream stream) throws Exception {
    StringBuilder builder = new StringBuilder();
    try (BufferedReader in = new BufferedReader(new InputStreamReader(stream))) {
        String line;
        while ((line = in.readLine()) != null) {
            builder.append(line); // + "\r\n"(no need, json has no line breaks!)
        }
        in.close();
    }
    System.out.println("JSON: " + builder.toString());
    return builder.toString();
}
Vozzie
  • 345
  • 2
  • 9
2

I know that this doesn't answer the question directly, but instead of using the HTTP connection library provided by Sun, you might want to take a look at Commons HttpClient, which (in my opinion) has a far easier API to work with.

matt b
  • 138,234
  • 66
  • 282
  • 345
  • 4
    I beg to differ. The API from Sun is much easier, as long as you do the really simple stuff. By simple stuff I mean just a GET without too much error handling, which is sufficient for a great number of cases. Of course HttpClient is far superior in functionality. – Michael Piefel May 31 '11 at 09:47
  • As of 2014, the best be might be [OkHttp](http://square.github.io/okhttp/) (which actually returns HttpURLConnection instances when opening a URL). Especially on Android it may help you avoid some nasty problems of both plain HttpURLConnection & Apache HttpClient. – Jonik Jan 29 '14 at 16:41
2

First check the response code and then use HttpURLConnection.getErrorStream()

Chris Dolan
  • 8,905
  • 2
  • 35
  • 73
1
InputStream is = null;
if (httpConn.getResponseCode() !=200) {
    is = httpConn.getErrorStream();
} else {
     /* error from server */
    is = httpConn.getInputStream();
}
sth
  • 222,467
  • 53
  • 283
  • 367
  • 5
    Won't other success codes like "201 CREATED" fail here? – Rich Oct 20 '14 at 13:00
  • Yes @Rich, that's why it'd better to do: `if (httpConn.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST) {` – AO_ Nov 21 '17 at 14:49
1

My running code.

  HttpURLConnection httpConn = (HttpURLConnection) urlConn;    
 if (httpConn.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST) {
                        in = new InputStreamReader(urlConn.getInputStream());
                        BufferedReader bufferedReader = new BufferedReader(in);
                        if (bufferedReader != null) {
                            int cp;
                            while ((cp = bufferedReader.read()) != -1) {
                                sb.append((char) cp);
                            }
                            bufferedReader.close();
                        }
                            in.close();

                    } else {
                        /* error from server */
                        in = new InputStreamReader(httpConn.getErrorStream());
                    BufferedReader bufferedReader = new BufferedReader(in);
                    if (bufferedReader != null) {
                        int cp;
                        while ((cp = bufferedReader.read()) != -1) {
                            sb.append((char) cp);
                        }
                        bufferedReader.close();
                    }    
                    in.close();
                    }
                    System.out.println("sb="+sb);
Durgesh Kumar
  • 935
  • 10
  • 17
  • that's really bad. The correct version should be setting in with regular or error stream and then having only ONE sequence out of the if(), which reads in. This code is uglily redundant. – zakmck Feb 06 '21 at 02:11
0

How to read 404 response body in java:

Use Apache library - https://hc.apache.org/httpcomponents-client-4.5.x/httpclient/apidocs/

or Java 11 - https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.html

Snippet given below uses Apache:

import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.util.EntityUtils;

CloseableHttpClient client = HttpClients.createDefault();
CloseableHttpResponse resp = client.execute(new HttpGet(domainName + "/blablablabla.html"));
String response = EntityUtils.toString(resp.getEntity());
Esakkiappan .E
  • 525
  • 6
  • 8