If you know what the effect is you can react accordingly.
You can retry if there’s a connection timeout but you can choose not to get stuck retrying if the file you want to send is corrupted.
You can use custom exceptions here but now you have to track what comes from where and catch accordingly.
You can argue that all alternatives are abused and turned into an antipattern.
People abuse checked exceptions and now you have 50 different checked exceptions spreading across functions when you could use a result type. Some checked exceptions have no real reason to exist: for example the constructors that take string arguments and throw an exception when the argument is unsupported when all you needed was an enum.
People abuse unchecked exceptions and now you don’t know what or where can explode for whatever reason so you add a top level try catch and have to debug on production and add try catches on the classes you find out you need granularity and you better wish reality doesn’t change otherwise it’s patch time again. Turns out that unreliable webservice does want a nested try catch rather than blowing up and trying again 10 minutes later.
People compromise: now they are writing “throws Exception” everywhere and now you can see what blows up. And everything can blow up. For whatever reason. At least you know now. Maybe it’s an SQLException, there was a timeout because the database got a nasty query. Maybe it’s an IOException because the file was locked for some reason. Maybe it was a null pointer? Who knows. It’s the unchecked exceptions abuse scenario made explicitly manifest. Also, please don’t turn exception messages into stringly typed exceptions.
People ditch exceptions and now use result types for everything. Now there’s question marks absolutely everywhere. Not knowing where, which and how to handle the results, exactly, they panic and call it a day. It will never happen anyways, right? Now they want to make the program runnable long term, reliable, and scalable, so they introduce threads to go blazingly fast and they have to catch the panics to stop the program from crashing. Back to exceptions we go.
I think the universal conclusion we can draw from all these cases is that it’s better to push IO, the allmother of exceptions, to the edge of your program, as far away as possible. Handle them however you like. Discretion is key.