17

Jackson's ObjectMapper#readValue member throws three checked exceptions:

IOException 
JsonParseException 
JsonMappingException

JsonParseException and JsonMappingException extend IOException. I want to wrap the two aforementioned child classes and throw my own custom exceptions, yet, the base class, IOException, being checked, requires me to either catch or throw it also.

It doesn't make sense for me to throw the IOException up to the calling layer, but, adversely, it's a smell if I hide it. My original thought was to not catch it and leave it to the caller/run-time exception mechanism to deal with it... yet, I don't want to have to force the caller to catch or specify.

What does one do in such a situation?

wulfgarpro
  • 6,666
  • 12
  • 69
  • 110

4 Answers4

15

Short answer: if you deal with IO, you deal with IOExceptions. If you don't deal with IO, then IOExceptions should be turned into unchecked exceptions, because they're symptoms of buggy code.


Longer answer:

readValue always takes a JsonParser, which may be wrapped around IO (e.g. a file or a URL). If you're dealing with IO, there's no way around dealing with IOExceptions, and you should either handle them or rethrow/pass them in some way. Anything can happen during IO, and you should be prepared to deal with the exceptions.

However, if you are sure that your JsonParser instances don't use IO (e.g. you used JsonFactory#createJsonParser(java.lang.String) to create a JSON parser on a string), you may assume that any IOExceptions you receive are bugs, either in your code or in Jackson. Usually, throwing an unchecked exception is then the proper way to deal with it:

ObjectMapper om = new ObjectMapper(/* whatever */);
JsonParser jp = JsonFactory.createJsonParser("{ \"foo\": \"bar\" }");
try {
    return om.readValue(jp);
} catch (IOException e) {
    throw new AssertionError("An IOException occurred when this was assumed to be impossible.");
}

Nota bene: my Java is rusty and I've never used Jackson, so consider the above block to be pseudocode.

In any case, you never need to declare AssertionError in throws, because they're unchecked exceptions. Everything that's a subclass of java.lang.RuntimeException or java.lang.Error doesn't need to be caught or rethrown explicitly. These exceptions are used for problems that are not expected to occur unless you're dealing with buggy code or when your VM's host is on fire.

  • 2
    +1: I like idea that you either have to deal with the exception or "declare it impossible" by wrapping in a RuntimeException. – Paul Cager Sep 19 '11 at 13:49
  • 2
    @Trinctorius - _"As to when each type is thrown, basic rule is that Jackson tries to pass underlying IOExceptions from read source (InputStream, Reader), and in limited number of cases where problem is related to low-level character decoding (invalid UTF-8 sequence)."_ I'm passing a `String` to `ObjectMapper#readValue`. From my perspective, no IO is really happening. – wulfgarpro Sep 20 '11 at 10:23
  • @wulfgar.pro I covered that exact situation as well, didn't I? ;) –  Sep 20 '11 at 12:04
  • 1
    @Trinctorius - I guess, for me, the confusion came from not knowing the exact reason as to why the `ObjectMapper#readValue` member throws `IOException`. Knowing that it's a possibility if passing in a stream an `IOException` might occur, clears things up. It doesn't seem correct that I should be forced to catch or declare the `IOException` if I'm using the override for `readValue` that accepts a `String`. – wulfgarpro Sep 20 '11 at 14:13
  • I understand the confusion and I agree `readValue` shouldn't throw an `IOException` for in case of `String`. I think Jackson is not really well-typed and should treat IO separately from non-IO (but that's probably because I have to do that as well in Haskell ;)). –  Sep 20 '11 at 14:17
  • @Tinctorius -- while I can see a case for suppressing IOExceptions for case where String is passed, this is due to the fact that after initializations, Strings are read via StringReader, passed as Reader, and from thereon core code has no knowledge that IOExceptions aren't expected. Caller could then try catching "impossible" exceptions, and maybe this is worth an RFE? It would make calls bit less consistent (and for me I sort of prefer consistency in API), but if users want it, they should request it. – StaxMan Sep 20 '11 at 16:52
  • Now that I took a better look, the Jackson API is not consistent. 1) Invalid UTF-8 sequences are parsing errors, never IO exceptions. Stuff was successfully read from the data source, but the stuff didn't make sense. 2) Declaring more throws than needed is inconsistent with the actual contract of a function. `String` instances are always valid, so `IOException` should never be thrown from ` T ObjectMapper#readValue(java.lang.String, java.lang.Class valueType)`. –  Sep 20 '11 at 17:39
  • @Tinctorius - thanks for validating my thoughts. I think the best course of action will be to catch and throw a RuntimeException as you have pointed out in your answer. – wulfgarpro Sep 20 '11 at 23:00
2

While this is not clearly documented within Jackson, rationale for IOExceptions is simple: IOExceptions from input sources (and output targets) are thrown as is -- since Jackson itself can not do anything for these, they are thrown as is. The only additional source for IOExceptions are things that are conceptually part of low-level (data-format independent) I/O handling, specifically, decoding of characters encodings like UTF-8.

From this, it seems relatively intuitive that JsonParsingException is for problems related to trying to parse invalid content; and JsonMappingException for problems at data-binding level. a

StaxMan
  • 113,358
  • 34
  • 211
  • 239
  • sure, but what about the native `IOExcetion` thrown in addition? – wulfgarpro Sep 21 '11 at 09:22
  • Only additional IOExceptions (not thrown by Readers/Writers, Input/OutputStreams) should be due to character decoding, which is logically part of Reader/Writers. Or am I misunderstanding your question? – StaxMan Sep 21 '11 at 16:41
  • So, if I'm passing in a `String`, I must catch/throw the additional `IOException` due to the underlying code decoding characters from my `String` into UTF-8? When would such an exception occur? If I was to pass in a UTF-16 character with no UTF-8 derivative (single byte)? – wulfgarpro Sep 21 '11 at 23:19
  • 2
    No, String characters are never decoded into UTF-8 (they are encoded as UTF-8 for writing), since internal representation (basically UCS-2) is used for parsing. So declaration of throwing IOException is a by-product of java.io.Reader declaring IOException being thrown (even if StringReader does not). ObjectMapper could conceivably add bogus catch block, and so we could get rid of this superfluous exception -- if this sounds like a good idea, maybe file a Jira RFE to request it? (so it can be discussed on list etc) – StaxMan Sep 22 '11 at 06:25
1

May patience got dried while wrapping dozen on methods in try/catch/rethrow boilerplate code. And I found Lombok can handle it for me. Hope it helps https://projectlombok.org/features/SneakyThrows

Krivda
  • 119
  • 2
  • 1
    That's throwing out the baby with the bathwater, as you taint your entire method with that annotation, not just the statement you're trying to silence. – Benny Bottema Jul 08 '21 at 14:35
  • 2
    @SneakyThrows doesn't __silence__ exceptions. It re-throws them as runtimes (unchecked), emending the original one as its source. These extreme cases, were you want __part__ of IOExceptions in method to be checked ones and __part__ - unchecked - is a really strange design, that might be a sign of violation of single responsibility principle. – Krivda Oct 20 '21 at 18:21
  • It silences the compiler obviously. – Benny Bottema Oct 20 '21 at 19:21
1

You should handle the IOException the same way you handle the json exceptions and wrap it. As the documentation of Jackson is lacking so much, you don't really know why any of them are thrown anyway (except for "an unknown error").

nfechner
  • 17,295
  • 7
  • 45
  • 64