jackson-databind: Stream not closed if IOException thrown while creating parser
v2.12.4
Hi, I’ve noticed that,  ObjectMapper#readValue(URL src, Class<T> valueType), for example, will not close the stream if reading from the URL causes, say, a FileNotFoundException. The exception is thrown in the createParser invocation, and so it is not caught by the _readMapAndClose call. I’m able to verify this by creating many instances to a URL that returns a 200—in these cases, netstat shows only one connection to the server. In the case of a URL returning a 404, there are many requests sitting in ESTABLISHED, and then proceed to hang in CLOSE_WAIT.
java.io.FileNotFoundException:
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1993)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1589)
	at java.base/java.net.URL.openStream(URL.java:1161)
	at com.fasterxml.jackson.core.TokenStreamFactory._optimizedStreamFromURL(TokenStreamFactory.java:211)
	at com.fasterxml.jackson.core.JsonFactory.createParser(JsonFactory.java:1057)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3479)
	at Main.main(Main.java:18)
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 17 (11 by maintainers)
@cowtowncoder Thanks for the quick fix. I can confirm that the read-case via
Fileis working correctly.@josh-byster I did a quick look at the initial issue and it seems to be an upstream issue in Java. Observed behavior with Java 17:
URLConnectiondepending on the URL that cannot be closed (no close method).sun.net.www.protocol.http.HttpURLConnectioninstance is created.URLConnectionis used to create the input stream viagetInputStream.HttpURLConnection#getInputStreamis mostly a proxy forgetInputStream0.getInputStream0creates the input stream and assignes it to itself.FileNotFoundExceptionexception is thrown due to HTTP 404 but the input stream isn’t closed. See note *1.Actually, there is a way to close the internal input stream by calling
disconnectof HttpURLConnection. With a bit of magic, all sockets are successfully closed (see example below). Unfortunately, we cannot use this workaround because it’s an internal Sun class.*1: AFAIK, the input stream is internally never closed but maybe I’m missing something. The code is mostly 15 years old and not well structured 😄 . The exception in HttpURLConnection seems to prevent the GC from freeing the
HttpURLConnectioninstance that was created inURL#openStream(maybe due to the input stream that is never closed).HttpURLConnectionmost likely own the socket too.I just stepped though a few files but you could continue to locate the core issue if you like, or create an upstream issue. If you have this issue at work, my best bet is to report it to your “Java vendor” for prio support. Let me know, otherwise I’ll eventually report the potential bug.
Tested with:
Example using nodejs server from above:
I do not understand what you mean by this. Do you want to merge the issues?
The managed stream by Jackson is only closed after successfully initialization when
_readMapAndCloseis executed. In the error case, the exception occurs during initialization and the stream is leaked:JsonFactory#createParser(File f)creates an input stream from the given file.ByteSourceJsonBootstrapper#constructParserinjackson.coreraise an IOException due to a failure indetectEncoding(see stack trace above).UTF8StreamJsonParserwhich should own the input stream, is never created and we never execute_readMapAndClose.JsonFactory#createParser(File f)is leaked because neither the initial method norUTF8StreamJsonParsercloses the stream.The same will most likely apply to
URLas well.A simple solution would be to create the stream with try-with-resources and call
readValue(InputStream src, ...). Otherwise, catching the exception from_createParserinJsonFactory#createParserand closing the stream in an error case would work too. I’m a little bit lost about the ownership of the input stream in the latter case becauseByteSourceJsonBootstrapperdoesn’t own the stream at this moment.