Handling Java exceptions in JRuby 2
At my enterprisey day job I’m finding the JRuby platform to be the perfect glue for bringing together a (sadly necessary) slew of Java libraries and middleware (3rd-party Ant tasks, Jakarta VFS, ServiceMix, Maven, Weblogic) to solve a complex problem crucial to our enterprise. Stellar support for exception handling is obviously important when integrating so many libraries and middleware so how well does JRuby help with this? Is it just a Java fair-weather friend?
Thankfully the answer is no. JRuby allows you to catch Java exceptions in Ruby code and handle them as if they were Ruby exceptions. These Ruby-ised exceptions provide, for free, a backtrace which spans your Ruby stack and your Java stack.
The least intrusive way to handle Java exceptions is to just pretend it’s a Ruby exception:
require 'java'
begin
# some bad Java mojo
rescue RuntimeError => e
puts "Java or Ruby exception: #{e}"
raise
endrequire ‘java’ loads in Java support from within JRuby and is necessary any time you want to call Java code.
All Java exceptions, when caught in the ether between your Ruby code that made the call and the Java code that fielded the code, is wrapped in a subclass of RuntimeError (more on this in a moment) and raised. By rescuing RuntimeError you are catching any Java exceptions that were thrown. Of course you’re also rescuing all Ruby RuntimeError exceptions that were thrown in the block too.
If you wish to segregate your Java exceptions from those raised from Ruby code then JRuby provides support for this too.
require 'java'
begin
# some bad Java mojo
rescue NativeException => e
puts "Native exception's cause: #{e.cause}"
endNativeException is a JRuby-specific Ruby exception which wraps any Java exception and provides access to it via the ‘cause’ method. It is the RuntimeError subclass caught in the first example. If you rescue a NativeException you will thus be rescuing all Java exceptions thrown from within the begin / rescue block but no others.
The cause methods provides access to the underlying Java exception. Unfortunately version 0.9.0 of JRuby has a small shortcoming with this. The Java exception returned by cause is not in a form that enables you to easily call its Java methods (ie its not proxied in JRuby). I think this is an enhancement that needs looking at and I have raised JIRA issue 104 accordingly.
In the mean time, the following code should give you and idea of how to access the underlying Java exception in a Ruby-friendly fashion:
begin
# some bad Java mojo
rescue NativeException => e
#creates a Ruby proxy for the Java exception
proxied_e = JavaUtilities.wrap(e.cause)
proxied_e.print_stack_trace
endThe final and most natural way of handling a Java exception is by making JRuby aware of it and then rescuing it like you would a regular Ruby exception:
require 'java'
include_class 'java.lang.IOException'
begin
# bad IOException-inducing mojo
rescue IOException => e
puts "Java IO exception: #{e.message}"
endinclude_class takes the Java class ‘java.lang.IOException’ and makes it available to your Ruby code through the name ‘IOException’.
By rescuing an IOException and assigning it to a variable ‘e’ you get an instance of NativeException with a cause method returning the native java.io.IOException. I think this is slightly confusing as you asked to rescue an IOException but got a NativeException - e should really be a proxied java.io.IOException but I can see some implementation difficulties with this when I think about it. I may raise this issue with Charles and Thomas.
Again e.cause isn’t proxied but a call to JavaUtilities.wrap(e.cause) will fix that.
So it’s that simple! JRuby provides great integration with Java’s exception system although it obviously needs a small amount of polish before it will be completely seamless. Onward and upward!
Comments
Comments are closed for this article
Interesting to hear you using JRuby for some enterprise glue. I'd be keen to know if you have any comments regarding performance of JRuby (as it stands today), though I guess a lot of glue-code doesn't end up in performance critical sections.
Slow. Slightly painful sometimes but not disasterously so if you use it for the right task. You pay for the cost of starting the JVM and then pay for the cost of booting JRuby and then finally it's your apps chance to be a bit slow. I think Charles mentioned at RailsConf EU that JRuby was somewhere around 10 times slower than Ruby which itself typically ranks in the bottom quartile of most language speed rankings.
Some things are slower than other others (as you'd expect). Parsing code is really slow (JIRB which is code-heavy takes 10 seconds to load on my PB 12" G4 - IRB takes half a second). String operations are seriously inefficient with ri taking absolutely forever to do anything. Strangely Rails' performance doesn't completely suck - not sure why that is.
Charles and Thomas are working really hard to whittle away the performance issues and I'm pretty sure they'll get there rather soonish. They seem to be giving 15% boosts in performance each recent minor release and there's the potential order-of-magnitude gains code compilation might give. Also, working full time on JRuby on Sun's dime certainly can't help but give more focus on this area.
You're spot-on in predicting where this glue code lived - somewhere performance was not even a periphery concern. It was used to interpret and execute a DSL that described the recipe for deploying all the components of our enterprise's SOA platform (based on ServiceMix/JBI & WLS). It's an operation seldom run in production and where much of the heavy JBI/J2EE lifting is done by standard Ant tasks supplied by the BEA & Logicblaze. It was sort of like a bespoke Capistrano for our particular enterpirsey circumstances.
Fun times.