oracle.sql.TIMESTAMP ClassCastException with SqlRowSet

After 2.5 days of smashing my head against Oracle hell (mixed metaphors?), I finally found a workaround for this strange ClassCastException I was having. I only found 2 or 3 Google results with this odd ClassCastException, so decided that my experience was significant enough to share!

ORIGINAL GOAL:
We had some code resembling the following:

String sqlString = “{some SQL statement that contains an ORDER BY clause}”;
Item item = itemDao.read(itemId);
getJdbcTemplate().query(sqlString,
    new Object[]{itemId},
    new RowCallbackHandler() {
        public void processRow(ResultSet rs) throws SQLException {
            ItemActivity itemActivity = new ItemActivity();
            populateItemActivity(itemActivity, rs, item);
        }
});
 

And populateItemActivity just does some stuff to the ItemActivity instance passed in, like:

public void populateItemActivity(ItemActivity itemActivity, ResultSet rs, Item item) throws SQLException{         
    itemActivity.setFileName(item.getName());
    itemActivity.setDate(rs.getTimestamp("LAST_MODIFIED_TIME"));
    itemActivity.setSize(item.getFileSize());      
    itemActivity.setAuditName(rs.getString("AUDIT_NAME"));
}

We discovered that the “AUDIT_NAME” field value was not populated for all returned rows, and in that case, the ItemActivity object should be populated with the “AUDIT_NAME” value in the record just before it, ordered by itemId and date. My fear was that the processRow(ResultSet rs) callback method was not guaranteed to process rows in the same order that they appeared in the resultset, so my attempt to copy back from the immediate prior record to copy its “AUDIT_NAME” value would be foiled and possibly incorrect.

Looking for a method that I could manipulate the entire ResultSet at one time, and so control the iteration and set a variable that I could copy between row processing, I found the JdbcTemplate.queryForRowSet(String sql, Object[] args) method, which returns a SqlRowSet I thought I could use to iterate explicitly.

We happen to be using Oracle, and a Timestamp value is one of the values in the returned SqlRowSet. This code was throwing the ClassCastException:

public void populateItemActivity(ItemActivity itemActivity, SqlRowSet rs, Item item) throws SQLException{                  
    itemActivity.setFileName(item.getName());
    oracle.sql.TIMESTAMP timestamp = (oracle.sql.TIMESTAMP) rs.getObject("LAST_MODIFIED_TIME ");  <--ClassCastException
    Date date =  (timestamp == null) ? null : new Date(timestamp.dateValue().getTime());   
    itemActivity.setDate(date);
    itemActivity.setSize(item.getFileSize());
    itemActivity.setAuditName(rs.getString("AUDIT_NAME"));
}

NOTE: the code at line 3 and 4 above I got from http://jira.springframework.org/browse/SPR-4886, who appeared to be having the same issue with SqlRowSet returning an oracle.sql.TIMESTAMP instead of a java.sql.Date with a call to rs.getObject.

No matter what I tried, I could not resolve the ClassCastException. Debugging in Eclipse showed that the Object returned from rs.getObject() was a oracle.sql.TIMESTAMP, but casting it to that same class resulted in the ClassCastException. Infuriating! Even gathering other developers, we could not figure this out.

So, NEW GOAL:
So I changed tack and found this other JdbcTemplate.query method with a different signature using a ResultSetExtractor, which allowed me to deal with the entire recordset (it's a ResultSet now instead of a SqlRowSet with the other attempt) at once (instead of a callback method for each row), letting me handle the row iteration explicitly:

getJdbcTemplate().query(sql,
        new Object[]{vaultItem.getVaultItemId()},
        new ResultSetExtractor() {                                             
          public Object extractData(ResultSet rs) throws SQLException,
            org.springframework.dao.DataAccessException {
                 String auditNameBuffer = null;
                 while (rs.next()){
                      ItemActivity itemActivity = new ItemActivity();                                                        
                      String auditName = rs.getString("AUDIT_NAME");
                      if ((null != auditName) ){
                          auditNameBuffer = auditName;
                      }                                                               
                      itemActivity.setAuditName(auditName);
                 }
                 return null;
            }      
     }
);

Project completed, but still don’t know what caused the ClassCastException.