You have to think about exception polymorphism. If you don't have exception inference, you also have to think about manually propagating exception contracts. Declaring it once is not a big deal, doing it over and over again is super annoying.
Let's say I have a Collection and want to do IO within that collection... If IOException was a runtime exception I could just write that code without handling it and an unsuspecting user of my new IOCollection would suddenly get an IOException. That means I need to explicitly deal with it in my class and can't add a serious exception to the behavior of the class.
Yes, it can still throw a runtime exception which is why the separation of the two is so important.
OTOH we have InputStream and OutputStream. Both throw an IOException for all their methods. So if I have one of them I should always handle the exception which is always the right thing to do...
But you might say, wait... What if I have a theoretical InputStream that will never throw an IOException?
Don't fret, we have that. It's called a ByteArrayInputStream and works roughly like this:
ByteArrayInputStream bos = new ByteArrayInputStream(new byte[100]);
int value = bos.read();
Notice I didn't use try and catch. Why? Because neither the constructor nor the read method throw an IOException which is legal in Javas polymorphism implementation. However, the following code won't compile without a catch exactly because I need to handle IOException for the generic case:
InputStream bos = new ByteArrayInputStream(new byte[100]);
int value = bos.read();
What's so hard about declaring throws?