15

Some 3rd party library swallowed an Exception:

String getAnswer(){
    try{
        // do stuff, modify instance state, maybe throw some exceptions
        // ...
        return computeAnswer(); 
    }catch (SomeException e){
        return null;
    }
}

As much as I want to change it into:

String getAnswer() throws SomeException{
    // do stuff, modify instance state, maybe throw some exceptions
    // ...
    return computeAnswer();
}

I can't, because the library is already packaged into a jar. So, is there a way to bring the exception back?

I don't need to rethrow, a stacktrace with exception and message would work too.

I don't think reflection would help here, Unsafe perhaps?

Yes I know I can use a debugger to find out what's happening, but that wouldn't be very useful if I need the exception at runtime for logging and stuff like that

tom91136
  • 8,662
  • 12
  • 58
  • 74
  • 5
    Call computeAnswer() instead of calling getAnswer()? – JB Nizet Sep 02 '15 at 16:45
  • 3
    If he's ready to use reflection, he does have access to the method. – JB Nizet Sep 02 '15 at 16:46
  • 1
    computeAnswer has private access (I made up computeAnswer() to demonstrate, in my case, it's just a bunch of mess that throws exception ) – tom91136 Sep 02 '15 at 16:46
  • 2
    @tom91136 As JB mentioned above: you can use reflection to "remove" the private access. – Nir Alfasi Sep 02 '15 at 16:48
  • 1
    If you feel this is a real design problem in the library, ask the developer of the library to improve it and release a better version. Or, if it's open-source, fork it and do it yourself. – JB Nizet Sep 02 '15 at 16:48
  • you can decompile it, modify the code, and compile it back to the jar:) – ZhongYu Sep 02 '15 at 17:06

8 Answers8

4

You can do it without reflection or AOP. The main idea is to throw another (unchecked) exception in the constructor of SomeException. There are some limitations (see at the end of this answer) but I hope it fits your needs.

You need to replace the SomeException with a new version (just create a SomeException.java file in the original package but in your src directory) with something like :

package com.3rdpartylibrary;

public class SomeException extends Exception {
    public static class SomeExceptionWrapperException extends RuntimeException {
        public SomeExceptionWrapperException(final SomeException ex) {
            super(ex.getMessage(), ex);
        }
    }

    public SomeException(final String message) {
        super(message);
        throw new SomeExceptionWrapperException(this); //<=== the key is here
    }
}

The SomeExceptionWrapperException has to be unchecked (inherit from RuntimeException or Error). It will be our wrapper to carry the SomeException accross the ugly 3rd party catch(...)

Then you can catch the SomeExceptionWrapperException in your code (and eventually rethrow the original SomeException:

//original, unmodifiable 3rdParty code, here as a example
public String getAnswer() {
    try {
        //some code
        throw new SomeException("a message");
    } catch (final SomeException e) {
        return null;
    }
}

//a wrapper to getAnswer to unwrapp the `SomeException`
public String getAnswerWrapped() throws SomeException {
    try {
        return getAnswer();
    } catch (final SomeExceptionWrapperException e) {
        throw (SomeException) e.getCause();
    }
}

@Test(expected = SomeException.class)
public void testThrow() throws SomeException {
    final String t = getAnswerWrapped();
}

The test will be green as the original SomeException, will be thrown.

Limitations:

This solution will not work if either :

  • if SomeException is in java.lang as you cannot replace java.lang classes (or see Replacing java class?)
  • if the 3rd party method has a catch(Throwable e) (which will be horrible and should motivate you to ignore the full 3rd party library)
Community
  • 1
  • 1
Benoît
  • 1,080
  • 11
  • 28
3

To solve this based on your constraints I would use aspects (something like AspectJ) and attach it to the creation of your exception, logging (or having it call some arbitrary) method then.

http://www.ibm.com/developerworks/library/j-aspectj/

Nathan Dunn
  • 447
  • 5
  • 16
1

If all you're looking for is to log the stacktrace + exception message, you could do that at the point you're throwing your exception.

See Get current stack trace in Java to get the stack trace. You can simply use Throwable.getMessage() to get the message and write it out.

But if you need the actual Exception within your code, you could try and add the exception into a ThreadLocal.

To do this, you would need a class like this that can store the exception:

package threadLocalExample;

public class ExceptionKeeper
{
    private static ThreadLocal<Exception> threadLocalKeeper = new ThreadLocal<Exception>();

    public static Exception getException()
    {
        return threadLocalKeeper.get();
    }

    public static void setException(Exception e)
    {
        threadLocalKeeper.set(e);
    }

    public static void clearException()
    {
        threadLocalKeeper.set(null);
    }
}

... then in your code which throws the Exception, the code that the 3rd party library calls, you can do something like this to record the exception before you throw it:

package threadLocalExample;

public class ExceptionThrower
{
    public ExceptionThrower()
    {
        super();
    }

    public void doSomethingInYourCode() throws SomeException
    {
        boolean someBadThing = true;
        if (someBadThing)
        {
            // this is bad, need to throw an exception!
            SomeException e = new SomeException("Message Text");

            // but first, store it in a ThreadLocal because that 3rd party
            // library I use eats it
            ExceptionKeeper.setException(e);

            // Throw the exception anyway - hopefully the library will be fixed
            throw e;
        }
    }
}

... then in your overall code, the one that calls the third party library, it can setup and use the ThreadLocal class like this:

package threadLocalExample;

import thirdpartylibrary.ExceptionEater;

public class MainPartOfTheProgram
{

    public static void main(String[] args)
    {

        // call the 3rd party library function that eats exceptions
        // but first, prepare the exception keeper - clear out any data it may have
        // (may not need to, but good measure)
        ExceptionKeeper.clearException();

        try
        {
            // now call the exception eater. It will eat the exception, but the ExceptionKeeper
            // will have it
            ExceptionEater exEater = new ExceptionEater();
            exEater.callSomeThirdPartyLibraryFunction();

            // check the ExceptionKeeper for the exception
            Exception ex = ExceptionKeeper.getException();
            if (ex != null)
            {
                System.out.println("Aha!  The library ate my exception, but I found it");
            }
        }
        finally
        {
            // Wipe out any data in the ExceptionKeeper. ThreadLocals are real good
            // ways of creating memory leaks, and you would want to start from scratch
            // next time anyway.
            ExceptionKeeper.clearException();
        }

    }

}

Beware of ThreadLocals. They have their use, but they are a great way of creating memory leaks. So if your application has a lot of threads that would execute this code, be sure to look at the memory footprint and make sure the ThreadLocals aren't taking up too much memory. Being sure to clear out the ThreadLocal's data when you know you no longer need it should prevent that.

Ian H
  • 46
  • 3
  • Well, this answer is awesome and taught me some new tricks. But the 3rd party library does not call any of my code, it's throwing exception in its own land and then swallowing it – tom91136 Sep 14 '15 at 09:25
  • bummer! The only thing you could do then is either the AOP option Nathan suggested or decompile option JB Nizet had. I've had to maintain my own copy of a single class that appeared in a 3rd-party library before. You could just do that and update that jar during your build vs. a total fork. It's a nasty hack, but if you don't want to introduce AOP into your app, it's your best option. – Ian H Sep 14 '15 at 13:52
  • Oh well, I guess AOP is the only practical solution in this case. What's sad about this is that it's not even an edge case, some legacy library does this kind of stuff all the time. The library I'm working on is from the Oracle JDK, specifically, the API for annotation processor. – tom91136 Sep 14 '15 at 19:26
1

JVMTI agent can help. See the related question.

I've made an agent that calls Throwable.printStackTrace() for every thrown exception, but you may easily change the callback to invoke any other Java method.

apangin
  • 92,924
  • 10
  • 193
  • 247
0

A rather dirty trick that could do the job with less effort than AOP or de-/recompile the JAR:

If you can copy the source code, you can create a patched version of the class in question with your version of the getAnswer method. Then put it on your classpath before the third party library that contains the unwanted version of getAnswer.

Problems could arise if SomeException is not a RuntimeException and other third party code calls getAnswer. In this situation I am not sure how the resulting behavior will be. But you could circumvent this by wrapping SomeException in a custom RuntimeException.

Lars Gendner
  • 1,816
  • 2
  • 14
  • 24
0

Could you not just use a reference variable to call that method, if the result is a null, then you can just display a message/call an exception, whatever you want?

Julian
  • 781
  • 1
  • 11
  • 32
  • From what I understand, the requestor needs the SomeException message and stacktrace : "I don't need to rethrow, a stacktrace with exception and message would work too" – Benoît Sep 17 '15 at 10:17
  • @Benoit is right, I need the messages and stacktrace otherwise I can't log anything. Having the original exception is the best of course but that seems to be hard to get. – tom91136 Sep 17 '15 at 13:24
0

if you're using maven, you would exclude packages of the library.

Dependency Exclusions.

I hope to be helpful

Harold Castillo
  • 2,006
  • 19
  • 23
0

If you have the source to the throwing class, you can add it "in the original package but in your src directory" using the technique as @Benoît has pointed out. Then just change

return null;

to

return e;

or

e.printStackTrace();

etc.

This would be quicker then making a new Exception.

ergonaut
  • 6,929
  • 1
  • 17
  • 47