Both
Java and
Ruby have
relatively sophisticated
exception handling built-in to the respective languages. Their
exception handling techniques have many similarities, but also have some differences in both keywords and in supported exception handling functionality.
JRuby allows mixing of Java classes with Ruby code and so it is no surprise that
JRuby supports handling of exceptions thrown by Java classes.
Ruby's equivalent of Java's
try-
catch-
finally is
begin-rescue-ensure, though Ruby throws in an additional clause with the
else clause. The
else
clause is executed when no declared
rescue
block is exercised. In other words, if there was an equivalent in Java (there isn't), it would be a block of code that is executed only when none of the the declared
catch
blocks were executed. Note that the Ruby
else
is different from Ruby's
ensure
or Java's
finally
in that it is only executed if no
rescue
(equivalent of Java
catch
) statement is executed while Ruby's
ensure
and Java's
finally
are always executed no matter which exceptions were caught or not caught.
Speaking of catching exceptions, Ruby does not have an equivalent of Java's
checked exceptions. JRuby (and
Groovy by the way) emulate this as well. Java and Ruby throw exceptions in the first place in a very similar manner, though Java uses the keyword
throw and Ruby uses the keyword
raise.
With the idea that most of the concepts of Java exception handling are highly similar to Ruby exception handling (the notable exceptions being the keywords used, the use of checked exceptions only in Java, and the use of a block of code executed only when no declared exception condition is encountered only in Ruby), it is time to move on to an examples of catching exceptions thrown by a Java class in JRuby code.
Java
exceptions, even
checked exceptions, do not need to be rescued/caught in JRuby. This is demonstrated in the following code listing in which an
SQLRecoverableException (a checked exception) might be thrown (and the called method is declared to throw its parent
SQLException
) but no explicit exception handling is implemented.
Listing 1 - JRuby Invocation of Java Method Throwing Exception
#!/usr/bin/env jruby -w
include Java
import java.sql.SQLRecoverableException
require 'C:\app\Dustin\product\11.1.0\db_1\jdbc\lib\ojdbc6.jar'
require 'C:\java\examples\jrubyExamples\jrubyExceptionHandling\lib\Dustin.jar'
include_class Java::dustin.examples.jruby.GenericOracleDatabaseAccessor
# 1. "Happy Path" query to demonstrate JRuby/Java interaction works fine
puts "ALL'S WELL"
accessor = GenericOracleDatabaseAccessor.new
puts "#{accessor.perform_database_query \
'jdbc:oracle:thin:@//localhost:1521/orcl',
'select first_name, last_name from employees',
['FIRST_NAME', 'LAST_NAME'].to_java(:string)}"
In the example above, there is a call to a custom Java class (
GenericOracleDatabaseAccessor
) and method (
performDatabaseQuery
). That class is shown in the next code listing.
Listing 2 - GenericOracleDatabaseAccessor.java
package dustin.examples.jruby;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import oracle.jdbc.pool.OracleDataSource;
/**
* Class used for demonstration purposes only and should not be used anywhere
* in which security or maintainability matter. The problems with this
* particular Java class include its being in the unnamed package, the extreme
* riskiness of supporting a generic query of any String passed into the method,
* lack of use of PreparedStatement, lack of verification of correct and
* non-null parameters before applying them, etc. In fact, this class is used
* here because its many problems make it easy to have it throw exceptions that
* the JRuby example code can catch and handle.
*/
public class GenericOracleDatabaseAccessor
{
final static String NEW_LINE = System.getProperty("line.separator");
/**
* Perform a generic database query. CAUTION: There are
* numerous reasons why this particular code sample should not be used in
* any real or production settings. It is used here only to demonstrate
* how JRuby supports handling of Java exceptions in a Ruby-like manner. It
* is not a good idea normally to pass in the JDBC URL. The JDBC exception
* handling is also intentionally poor in this example. See class description
* for other failings (mostly intentional) of this class and method..
*
* @param jdbcUrl The JDBC URL for the database connection; a default is
* attempted if this is null.
* @param queryString The query statement to be run against the database.
* @param columnNames Column Names whose values are to be returned in
* resulting String.
* @throws SQLException Checked exception related to use of JDBC.
*/
public String performDatabaseQuery(
final String jdbcUrl,
final String queryString,
final String[] columnNames) throws SQLException
{
final OracleDataSource datasource = new OracleDataSource();
final String url = jdbcUrl == null
? "jdbc:oracle:thin:@//localhost:1521/orcl"
: jdbcUrl;
datasource.setURL(url);
datasource.setUser("hr");
datasource.setPassword("hr");
final Connection connection = datasource.getConnection();
final Statement queryStatement = connection.createStatement();
final ResultSet resultSet = queryStatement.executeQuery(queryString);
final StringBuilder returnString = new StringBuilder();
while (resultSet.next())
{
for (final String columnName : columnNames)
{
returnString.append(resultSet.getObject(columnName)).append(" ");
}
returnString.append(NEW_LINE);
}
resultSet.close();
queryStatement.close();
connection.close();
return returnString.toString();
}
}
As the code listing for
GenericOracleDatabaseAccessor
above indicates, the main method that is called on that class is called
performDatabaseQuery
and its accepts three parameters. The JRuby code could have called this method in a traditional Java-style call, but I chose to use a more
Ruby-stylistic method call with underscores separating the multiple words in the method name along with parameters passed to the method in Ruby style.
The Java method
performDatabaseQuery
is declared to throw
SQLException (parent of
SQLRecoverableException). However, the calling JRuby code did not need to explicitly handle the exception. The above code ran correctly, but how would the exception be handled if it did get thrown? The next JRuby sample code intentionally specifies a non-existent host to force an exception to be thrown.
Listing 3 - Java Exception Thrown But Not Explicitly Handled in JRuby
#!/usr/bin/env jruby -w
include Java
import java.sql.SQLRecoverableException
require 'C:\app\Dustin\product\11.1.0\db_1\jdbc\lib\ojdbc6.jar'
require 'C:\java\examples\jrubyExamples\jrubyExceptionHandling\lib\Dustin.jar'
include_class Java::dustin.examples.jruby.GenericOracleDatabaseAccessor
puts "EXCEPTION WITHOUT HANDLING"
accessor = GenericOracleDatabaseAccessor.new
puts "#{accessor.perform_database_query \
'jdbc:oracle:thin:@//unknownhost:1521/orcl',
'select first_name, last_name from employees',
['FIRST_NAME', 'LAST_NAME'].to_java(:string)}"
Running the above JRuby code leads to the following output.
Although the exception thrown from the Java class was never explicitly handled in the code, the JRuby output shown directly above indicates that the exception is ultimately "handled" by displaying stack trace information. Of particular interest is the
NativeException, the JRuby exception that
wraps Java exceptions.
Listing 4 - JRuby Code Explicitly Catching Java Exception
#!/usr/bin/env jruby -w
include Java
import java.sql.SQLRecoverableException
require 'C:\app\Dustin\product\11.1.0\db_1\jdbc\lib\ojdbc6.jar'
require 'C:\java\examples\jrubyExamples\jrubyExceptionHandling\lib\Dustin.jar'
include_class Java::dustin.examples.jruby.GenericOracleDatabaseAccessor
puts "RUBY CATCHING OF JAVA EXCEPTION"
accessor = GenericOracleDatabaseAccessor.new
begin
puts "#{accessor.perform_database_query \
'jdbc:oracle:thin:@//unknownhost:1521/orcl',
'select first_name, last_name from employees',
['FIRST_NAME', 'LAST_NAME'].to_java(:string)}"
rescue SQLRecoverableException => sqlRecoverableException
puts "#{sqlRecoverableException}"
puts "Message: #{sqlRecoverableException.message}"
puts "Backtrace: #{sqlRecoverableException.backtrace}"
end
The code in Listing 4 looks like Ruby code, but explicitly catches a Java exception (
SQLRecoverableException). The output from running this is shown next.
The same exception can be caught with a Ruby-specific exception as well. This is shown with the next code listing (Listing 5).
Listing 5 - Catching Java Exception in JRuby as Ruby Exception
#!/usr/bin/env jruby -w
include Java
import java.sql.SQLRecoverableException
require 'C:\app\Dustin\product\11.1.0\db_1\jdbc\lib\ojdbc6.jar'
require 'C:\java\examples\jrubyExamples\jrubyExceptionHandling\lib\Dustin.jar'
include_class Java::dustin.examples.jruby.GenericOracleDatabaseAccessor
puts "CATCHING INTENTIONAL EXCEPTION WITH RUBY'S RUNTIME ERROR"
accessor = GenericOracleDatabaseAccessor.new
begin
puts "#{accessor.perform_database_query \
'jdbc:oracle:thin:@//unknownhost:1521/orcl',
'select first_name, last_name from employees',
['FIRST_NAME', 'LAST_NAME'].to_java(:string)}"
rescue RuntimeError => runtimeError
puts "#{runtimeError}"
puts "Message: #{runtimeError.message}"
puts "Backtrace: #{runtimeError.backtrace}"
end
The output for the above code listing is shown next.
Ruby provides the ability to catch multiple exceptions in the same
rescue
clause (a
feature talked about for
Java SE 7). With JRuby, we can leverage this functionality to use the same
rescue
clause to catch a Java-based exception and a Ruby-based exception. This is demonstrated with Listing 6.
Listing 6 - Catching Java and Ruby Exceptions in Same rescue Clause
#!/usr/bin/env jruby -w
include Java
import java.sql.SQLRecoverableException
require 'C:\app\Dustin\product\11.1.0\db_1\jdbc\lib\ojdbc6.jar'
require 'C:\java\examples\jrubyExamples\jrubyExceptionHandling\lib\Dustin.jar'
include_class Java::dustin.examples.jruby.GenericOracleDatabaseAccessor
puts "CATCHING/RESCUING RUBY/JAVA EXCEPTIONS IN SAME RESCUE BLOCK"
accessor = GenericOracleDatabaseAccessor.new
begin
puts "#{accessor.perform_database_query \
'jdbc:oracle:thin:@//unknownhost:1521/orcl',
'select first_name, last_name from employees',
['FIRST_NAME', 'LAST_NAME'].to_java(:string)}"
rescue RuntimeError, SQLRecoverableException => sqlError
puts "#{sqlError}"
puts "Message: #{sqlError.message}"
puts "Backtrace: #{sqlError.backtrace}"
end
The output for this code listing is now shown.
Ruby adds some extra possibilities to exception handling. Besides the
rescue
keyword, Ruby also provides the ability to execute a block of code if no
rescue
block is executed. This is indicated with the
else
keyword. This is different from
ensure
(Ruby's equivalent of Java's
finally
) because it only executes if none of the declared
rescue
blocks is executed.
The next code listing demonstrates this and the screen shot following the code listing shows what the output looks like upon execution.
Listing 7 - Demonstrating Ruby's else and ensure Keywords
#!/usr/bin/env jruby -w
include Java
import java.sql.SQLRecoverableException
require 'C:\app\Dustin\product\11.1.0\db_1\jdbc\lib\ojdbc6.jar'
require 'C:\java\examples\jrubyExamples\jrubyExceptionHandling\lib\Dustin.jar'
include_class Java::dustin.examples.jruby.GenericOracleDatabaseAccessor
puts "USING RUBY'S ELSE AND ENSURE"
accessor = GenericOracleDatabaseAccessor.new
begin
puts "#{accessor.perform_database_query \
'jdbc:oracle:thin:@//localhost:1521/orcl',
'select first_name, last_name from employees',
['FIRST_NAME', 'LAST_NAME'].to_java(:string)}"
rescue RuntimeError, SQLRecoverableException => sqlError
puts "#{sqlError}"
puts "Message: #{sqlError.message}"
puts "Backtrace: #{sqlError.backtrace}"
else
puts "ELSE: Data Access performed without exception!"
ensure
puts "ENSURE: I am always called whether rescue or else is invoked."
end
ConclusionException handling is just one area in which JRuby makes it easy to mix Java and Ruby. This blog posting has covered different ways to handle exceptions thrown by Java classes in JRuby code. Additional resources with different and greater details are listed next.
Additional ResourcesHandling Java Exceptions in JRuby (September 2006)
Dealing with Java Exceptions in JRuby (May 2007)
Java and Ruby Working Together (see "Exception handling" section)
Calling Java from JRuby (Wiki page that was last updated 21 March 2009 at time of this writing)
Using Java Classes in JRuby (September 2007)
Ruby Exceptions: Ruby Study NotesRuby's Exception Hierarchy (September 2006)
Programming Ruby:
Exceptions, Catch, and Throw (2001)