diff --git a/.changes/next-release/bugfix-UrlConnectionClient-4dd3b0c.json b/.changes/next-release/bugfix-UrlConnectionClient-4dd3b0c.json new file mode 100644 index 000000000000..c840199a6db5 --- /dev/null +++ b/.changes/next-release/bugfix-UrlConnectionClient-4dd3b0c.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "Url Connection Client", + "contributor": "", + "description": "Allow retry when URL Connection HTTP Client encounters a NullPointerException wrapped in a RuntimeException" +} diff --git a/http-clients/url-connection-client/src/main/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.java b/http-clients/url-connection-client/src/main/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.java index 44f2724836fc..6537ec24753a 100644 --- a/http-clients/url-connection-client/src/main/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.java +++ b/http-clients/url-connection-client/src/main/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.java @@ -366,6 +366,11 @@ private Optional getAndHandle100Bug(Supplier supplier, boolean failOn1 try { return Optional.ofNullable(supplier.get()); } catch (RuntimeException e) { + if (e.getCause() instanceof NullPointerException) { + throw new UncheckedIOException(new IOException( + "Unexpected NullPointerException when calling HttpURLConnection", e)); + } + if (!exceptionCausedBy100HandlingBug(e)) { throw e; } @@ -431,6 +436,12 @@ private static int getResponseCodeSafely(HttpURLConnection connection) throws IO return connection.getResponseCode(); } catch (NullPointerException e) { throw new IOException("Unexpected NullPointerException when trying to read response from HttpURLConnection", e); + } catch (RuntimeException e) { + if (e.getCause() instanceof NullPointerException) { + throw new IOException("Unexpected NullPointerException when trying to read response from " + + "HttpURLConnection", e); + } + throw e; } } diff --git a/http-clients/url-connection-client/src/test/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClientWithCustomCreateWireMockTest.java b/http-clients/url-connection-client/src/test/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClientWithCustomCreateWireMockTest.java index e6ac8afe5546..55b4410c6f36 100644 --- a/http-clients/url-connection-client/src/test/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClientWithCustomCreateWireMockTest.java +++ b/http-clients/url-connection-client/src/test/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClientWithCustomCreateWireMockTest.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.UncheckedIOException; import java.net.HttpURLConnection; import java.net.ProtocolException; import java.net.URL; @@ -75,6 +76,46 @@ public int getResponseCode() { .hasCauseInstanceOf(NullPointerException.class); } + @Test + public void testGetResponseCodeRuntimeExceptionWrappingNpeIsWrappedAsIo() throws Exception { + connectionInterceptor = safeFunction(connection -> new DelegateHttpURLConnection(connection) { + @Override + public int getResponseCode() { + throw new RuntimeException(new NullPointerException("this.http is null")); + } + }); + + assertThatThrownBy(() -> testForResponseCode(HttpURLConnection.HTTP_OK)) + .isInstanceOf(IOException.class) + .hasCauseInstanceOf(RuntimeException.class); + } + + @Test + public void testGetOutputStreamRuntimeExceptionWrappingNpeIsWrappedAsIo() throws Exception { + connectionInterceptor = safeFunction(connection -> new DelegateHttpURLConnection(connection) { + @Override + public OutputStream getOutputStream() { + throw new RuntimeException(new NullPointerException("this.http is null")); + } + }); + + assertThatThrownBy(() -> testForResponseCode(HttpURLConnection.HTTP_OK)) + .isInstanceOf(UncheckedIOException.class); + } + + @Test + public void testGetInputStreamRuntimeExceptionWrappingNpeIsWrappedAsIo() throws Exception { + connectionInterceptor = safeFunction(connection -> new DelegateHttpURLConnection(connection) { + @Override + public InputStream getInputStream() { + throw new RuntimeException(new NullPointerException("this.http is null")); + } + }); + + assertThatThrownBy(() -> testForResponseCode(HttpURLConnection.HTTP_OK)) + .isInstanceOf(UncheckedIOException.class); + } + private class DelegateHttpURLConnection extends HttpURLConnection { private final HttpURLConnection delegate;