Skip to main content

Backwards compatibility, adding 'throws' to an object constructor

6 replies [Last post]
rmccullough
Offline
Joined: 2007-09-17
Points: 0

The product I am working on guarantees a high level of backwards compatibility for 2 releases into the future.

A change was required in the current version that added a 'throws' to an object constructor.

ie

  public ObjectFactory(ObjectFactory Connection)<br />
  {<br />
    super(Connection);<br />
    Init();<br />
  }

changed to
  public ObjectFactory(ObjectFactory Connection) throws javax.xml.rpc.ServiceException, javax.xml.soap.SOAPException<br />
  {<br />
    super(Connection);<br />
    Init();<br />
  }

It seems jars compiled against the previous version that did not have the try/catch around the constructor are still able to work with the new version. Is this expected? What happens if a ServiceException of SOAPException is thrown?

Thank you for your time.

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
prunge
Online
Joined: 2004-05-06
Points: 0

I think that all Exceptions are equal in the JVM - checked or unchecked - the difference between these two types of exceptions is at compile time only.

Therefore if you manage to get past the compile stage this should happily run and when ServiceException or SOAPException is thrown from the constructor it will just propagate up the call stack (just like a RuntimeException) until something catches it or it gets to the top where you will get a stack trace on the console (unless you have registered a custom uncaught exception handler on the thread).

rmccullough
Offline
Joined: 2007-09-17
Points: 0

FYI, I am using NetBeans as my IDE and it requires me to handle exceptions by either putting them in a try/catch or putting a throws statement on the method.

I think I need to put word my question a different way.

Lets say 3 months ago your jar file was compiled against the api jar that did not have the throws for the soap and service exceptions. Now, you upgrade to the new api jar file but do not recompile your jar file. You jar file does not handle the exceptions that can be thrown from the ObjectFactory constructor. It appears to run fine though.

We are telling customers that out api will be backwards compatible for 2 releases. If they recompile their code in netbeans it will report an error because of the un-caught exceptions from the ObjectFactory constructor. However, if you do not recompile, it seems your old jar file still works.

Does this make more sense?

Is this ok? What happens if the exception is thrown and you do not have code to catch it or throw it?

prunge
Online
Joined: 2004-05-06
Points: 0

If such a change (adding checked Exception to method or constructor in public API) were made, I would consider it not backward compatible.

Binaries would still run - that is if a customer had their code compiled against your old API (without the checked exception) but run against your new code (with the checked exception) it would still run. However, if your new API code actually threw a checked exception the customer code would not catch it and this exception would get propogated upwards unless they are catching java.lang.Exception or java.lang.Throwable. I'm guessing this is because the JVM, as opposed to the compiler, does not know the difference between checked and unchecked exceptions.

The customer's source code, as you said, will not compile against the new version of your API since it now declares a checked exception.

So ultimately, adding a checked exception to a public API method or constructor should not be considered a backward compatible change. If you need to do something like this, consider throwing an unchecked exception (one that extends RuntimeException), possibly wrapping javax.xml.rpc.ServiceException and javax.xml.soap.SOAPException when needed using the exception chaining mechanism.

rmccullough
Offline
Joined: 2007-09-17
Points: 0

Can you provide an example?

prunge
Online
Joined: 2004-05-06
Points: 0

Let's say this is version 1.0 of my library, a public API that others will use:

[code]
//Version 1.0
public class MyLibrary
{
public MyLibrary()
{
}

public void doSomething()
{
System.out.println("I am doing something");
}
}
[/code]

A user writes this code and compiles it to use version 1.0 of my library:

[code]
public class UserApplication
{
public static void main(String... args)
{
MyLibrary lib = new MyLibrary();
lib.doSomething();
}
}
[/code]

Let's assume my library is in mylibrary-1.0.jar and the user has this JAR on his/her classpath, and has built a JAR userapplication.jar.

Running results in the message "I am doing something" to be printed to the console - no problems so far.

Now I write version 1.1 of my library that declares the constructor throws a checked exception:

[code]
//Version 1.1 - new version with checked exception
public class MyLibrary
{
public MyLibrary()
throws IOException
{
//Do stuff here that might result in IOException...
}

public void doSomething()
{
System.out.println("I am doing something");
}
}
[/code]

I build a JAR with the new version of MyLibrary called mylibrary-1.1.jar and give it to the user.

The user now, assuming MyLibrary version 1.1 is backwards compatible with version 1.0, simply replaces the JAR, so his/her classpath has mylibrary-1.1.jar and userapplication.jar - this is done without recompilation from source.

The application still runs properly when no IOException is thrown, however, if the user now tries to compile from source against version 1.1 of MyLibrary he/she will get a compile error:

[pre]
UserApplication.java:6: unreported exception java.io.IOException; must be caught
or declared to be thrown
MyLibrary lib = new MyLibrary();
[/pre]

Assuming the user does not try to recompile from source and MyLibrary [i]does[/i] throw an I/O exception, the JVM allows this too - the exception just propogates upwards since user code does not catch it.

If MyLibrary must throw an exception from the constructor, it should use an unchecked exception (one that extends java.lang.RuntimeException) that wraps the original I/O exception.

[code]
//Version 1.1 - what it should be
public class MyLibrary
{
/**
* Document unchecked exceptions that might be thrown.
*
* @throws MyLibraryException if an error occurs.
*/
public MyLibrary()
{
try
{
//Do stuff here that might result in IOException...
}
catch (IOException e)
{
throw new MyLibraryException("Error initializing MyLibrary.", e);
}
}

public void doSomething()
{
System.out.println("I am doing something");
}
}

//Declare new exception type - or you could reuse an existing one
public class MyLibraryException
extends RuntimeException
{
public MyLibraryException(String message, Throwable cause)
{
super(message, cause);
}
}
[/code]

Wrapping the original checked exception means no information is lost debugging stack traces.

rmccullough
Offline
Joined: 2007-09-17
Points: 0

I think I get it. This is what I have done (FYI, the ObjectFactory class inherits from the Util class):
[code] public Util(Util Connection)
{
try {
setServerURL(Connection.getServerURL());
} catch (javax.xml.rpc.ServiceException ex) {
throw new RuntimeException("Clone Exception: " + ex.getMessage(), ex);
}
// snipped some code for simplcity
try {
setHeaderInfo(UTCFormat.format(CurTime.getTime()),SessionID);
} catch (javax.xml.soap.SOAPException ex) {
throw new RuntimeException("Clone Exception: " + ex.getMessage(), ex);
}
}[/code]

Now I know I did not create my own exception class (I actually already have one but it does not extend RuntimeException), but is this essentially what you are saying? I spoke with a guy here at work (one of the 2 other guys that use Java in the entire company) and he said that this should work to keep backward compatibility.