[Java] Throw a PATCH request with HttpURLConnection

2 minute read

When I wanted to perform API communication when creating an Android application, I had to translate it and implement it without using the OSS library. Therefore, I created the communication part by using HttpURLConnection.

However, an error occurred when I wanted to send a PATCH request. It seems that HttpURLConnection can’t make PATCH request. It’s a lie…

There is also a method to impersonate using the X-HTTP-Method-Override header, but it seems to be uncertain because there is a dependency on the server side. https://stackoverflow.com/questions/25163131/httpurlconnection-invalid-http-method-patch

What is the OSS library doing? So, refer to the public codeofHTTPclientlibraryJersey I implemented it. It is a little old library, but I have confirmed the operation.

Implementation example

Implement the HttpClient class as shown below. This time, we assume Bearer authentication.

HttpClient.kt



internal class HttpClient {
    companion object {

        fun patch(url: String, accessToken: String? = null, body: String? = null): Pair<Int, String?> {
            var responseJsonString: String? = null
            val urlTmp = URL(url)
            val con = urlTmp.openConnection() as HttpURLConnection
            try {
                setRequestMethodViaJreBugWorkaround( con, "PATCH")
                con.instanceFollowRedirects = false
                con.doOutput = true
                con.connectTimeout = 60000
                con.readTimeout = 60000
                con.setRequestProperty("Content-Type", "application/json")
                con.setRequestProperty("Accept", "application/json")
                if(accessToken != null) con.setRequestProperty("Authorization", "Bearer $accessToken")
                if(body != null) {
                    val os: OutputStream = con.outputStream
                    val ps = PrintStream(os)
                    ps.print(body)
                    ps.close()
                }
                val reader: BufferedReader
                reader = if (con.responseCode == HttpURLConnection.HTTP_OK) {
                    BufferedReader(InputStreamReader(con.inputStream, "UTF-8"))
                } else {
                    BufferedReader(InputStreamReader(con.errorStream, "UTF-8"))
                }
                responseJsonString = reader.readLine()
                con.disconnect()
            } catch (e: Exception) {
                e.printStackTrace()
                Log.e("ApiClientException", e.toString())
            }
            return con.responseCode to responseJsonString
        }

        private fun setRequestMethodViaJreBugWorkaround(httpURLConnection: HttpURLConnection, method: String) {
            try {
                httpURLConnection.requestMethod = method // Check whether we are running on a buggy JRE
            } catch (pe: ProtocolException) {
                try {
                    AccessController.doPrivileged(PrivilegedExceptionAction<Any?> {
                        try {
                            httpURLConnection.requestMethod = method
                            // Check whether we are running on a buggy
                            // JRE
                        } catch (pe: ProtocolException) {
                            var connectionClass: Class<*>? = httpURLConnection.javaClass
                            val delegateField: Field?
                            try {
                                delegateField = connectionClass!!.getDeclaredField("delegate")
                                delegateField.isAccessible = true
                                val delegateConnection = delegateField[httpURLConnection] as HttpURLConnection
                                setRequestMethodViaJreBugWorkaround(delegateConnection, method)
                            } catch (e: NoSuchFieldException) {
                                // Ignore for now, keep going
                            } catch (e: IllegalArgumentException) {
                                throw RuntimeException(e)
                            } catch (e: IllegalAccessException) {
                                throw RuntimeException(e)
                            }
                            try {
                                var methodField: Field
                                while (connectionClass != null) {
                                    try {
                                        methodField = connectionClass.getDeclaredField("method")
                                    } catch (e: NoSuchFieldException) {
                                        connectionClass = connectionClass.superclass
                                        continue
                                    }
                                    methodField.isAccessible = true
                                    methodField[httpURLConnection] = method
                                    break
                                }
                            } catch (e: java.lang.Exception) {
                                throw RuntimeException(e)
                            }
                        }
                        null
                    })
                } catch (e: PrivilegedActionException) {
                    val cause: Throwable? = e.causeif (cause is RuntimeException) {
                         throw cause
                     } else {
                         throw RuntimeException(cause)
                     }
                 }
             }
         }
     }
}

Example of use

Import the above HttpClient and use it as follows.

fun patchRequestSample() {
     val (statusCode, responseJsonString) = HttpClient.patch("https://hoge/users/10", accessToken ="xxxx", body = sampleJson.toString())
}

The status code is returned in statusCode and the response is returned in responseJsonString.

Since I found only the method of overriding the header even after checking it, I will leave it as a memo.