When developing a custom HTTP utility using Java's built-in HttpURLConnection, a common issue can arise with connection management. While a GET request might successfully retrieve data, the server may report an abrupt connection termination after the client application closes.
For instance, a backend service built with Python/Django might log a ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host immediately after the Java client application terminates. This error indicates that the underlying socket connection was not closed gracefully from the client's side.
Consider the following implementation of a GET request method within a client utility class:
public class HttpClient {
private int connectionTimeout = 30000;
private int readTimeout = 30000;
public void performGet(String urlString, Map<String, String> headers, ApiResponse apiResponse) {
int statusCode = 0;
InputStream inStream = null;
BufferedReader bufReader = null;
Map<String, String> responseHeaders = new HashMap<>();
try {
URL targetUrl = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) targetUrl.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(connectionTimeout);
connection.setReadTimeout(readTimeout);
// Apply custom headers
if (headers != null) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
connection.setRequestProperty(entry.getKey(), entry.getValue());
}
}
connection.connect();
statusCode = connection.getResponseCode();
if (statusCode == 200) {
inStream = connection.getInputStream();
bufReader = new BufferedReader(new InputStreamReader(inStream));
StringBuilder contentBuilder = new StringBuilder();
String line;
while ((line = bufReader.readLine()) != null) {
contentBuilder.append(line).append("\n");
}
apiResponse.setBody(contentBuilder.toString());
// --- Incorrect Resource Closing Order ---
inStream.close();
bufReader.close();
connection.disconnect();
} else {
apiResponse.setError(connection.getResponseMessage());
connection.disconnect();
}
// Process response headers...
Map<String, List<String>> rawHeaders = connection.getHeaderFields();
// ...code to parse headers into apiResponse...
apiResponse.setStatusCode(statusCode);
} catch (IOException e) {
// Exception handling, closing resources if they were opened...
}
}
}
The root cause of the server-side ConnectionResetError lies in the sequence in which resources are closed in the client code. The problematic sequence, highlighted in the code above, involves closing the input stream and reader before closing the HttpURLConnection object itself.
By placing connection.disconnect() last, the underlying socket connection is kept alive for potential reuse by the JVM's internal connection pooling mechanism. This is the default behavior to optimize performance for subsequent requests to the same host. However, when the Java application exits, this lingering connection is terminated abruptly by the operating system, which sends a TCP RST (reset) packet to the server. The server interprets this as an ungraceful shutdown, hence the ConnectionResetError.
To prevent this, the connection must be explicit closed first if you do not intend to reuse the socket. This ensures a graceful termination of the TCP session before the application exits. The correct closing order is to disconnect first, then close the stream-based resources.
if (statusCode == 200) {
inStream = connection.getInputStream();
bufReader = new BufferedReader(new InputStreamReader(inStream));
StringBuilder contentBuilder = new StringBuilder();
String line;
while ((line = bufReader.readLine()) != null) {
contentBuilder.append(line).append("\n");
}
apiResponse.setBody(contentBuilder.toString());
// --- Correct Resource Closing Order ---
<strong>connection.disconnect();</strong>
inStream.close();
bufReader.close();
} else {
apiResponse.setError(connection.getResponseMessage());
connection.disconnect();
}
By calling disconnect() before closing the stream and reader, you signal that the connection should be closed immediately. The socket will be properly terminated, and the server will no longer encounter a forced connection reset when the client process ends. This approach is crucial for short-lived scripts or applications that do not maintain a persistent connection pool.