329

In Java I'm trying to test for a null value, from a ResultSet, where the column is being cast to a primitive int type.

int iVal;
ResultSet rs = magicallyAppearingStmt.executeQuery(query);
if (rs.next()) {
  if (rs.getObject("ID_PARENT") != null && !rs.wasNull()) {
    iVal = rs.getInt("ID_PARENT");
  }
}

From the code fragment above, is there a better way to do this, and I assume that the second wasNull() test is redundant?

Educate us, and Thanks

ian_scho
  • 5,906
  • 9
  • 35
  • 51
  • 23
    I found this question because I have a nullable column in a database and it's represented by an Integer in Java. You would think that having a nullable numeric column in a database would be common enough that the ResultSet API would accommodate it a little more elegantly. – spaaarky21 Jan 04 '12 at 19:17
  • I'm not posting this as an answer because it's tangential and far from universal: My usual solution to this is to put `IF(colName = NULL, 0, colName) AS colName` in the `SELECT` statement (preferably in a stored proc). Philosophically this comes down to whether the DB should conform to the app, or vice versa. Since SQL handles NULLs easily and many SQL consumers do not (i.e. `java.sql.ResultSet`), I opt to handle it at the DB when possible. (This, of course, assumes that conceptually NULL and zero are equivalent for your purposes.) – s.co.tt Oct 30 '19 at 03:05
  • You could do this easily in SQL without worrying about SQL `NULL` and Java `null`. Just write `... IS NOT NULL` and retrieve this as a `boolean` value in JDBC. – Tech Expert Wizard Nov 28 '20 at 14:23

13 Answers13

439

The default for ResultSet.getInt when the field value is NULL is to return 0, which is also the default value for your iVal declaration. In which case your test is completely redundant.

If you actually want to do something different if the field value is NULL, I suggest:

int iVal = 0;
ResultSet rs = magicallyAppearingStmt.executeQuery(query);
if (rs.next()) {
    iVal = rs.getInt("ID_PARENT");
    if (rs.wasNull()) {
        // handle NULL field value
    }
}

(Edited as @martin comments below; the OP code as written would not compile because iVal is not initialised)

Richard
  • 9,740
  • 2
  • 23
  • 15
  • 2
    I've just found the same statement in docs. It worth a separate thread on SO imho. (http://java.sun.com/j2se/1.4.2/docs/api/java/sql/ResultSet.html#getObject(int)) – Roman May 27 '10 at 10:58
  • 6
    @Roman - see the javadoc for getInt in ResultSet: "Returns: the column value; if the value is SQL NULL, the value returned is 0 " – Cowan May 27 '10 at 11:53
  • 213
    The truth is, indeed, ridiculous. `getInt()` should be `getInteger()` which returns an `Integer` that is `null` if the DB value is `null`. The devs really messed this one up. – ryvantage Mar 07 '14 at 20:00
  • 3
    Just for the record: I don't think `iVal` has a default value of 0 (at least not in this snippet). In case of a `NULL` column, `iVal` is just not initialized and accessing it would yield a compiler error. – martin Oct 08 '14 at 13:34
  • 2
    `wasNull()` is problematic when doing concurrent reads from the same ResultSet. Don't dwell on the past ;) – AlikElzin-kilaka Dec 30 '14 at 13:24
  • @ryvantage I don't think devs messed up by returning '0' for NULL instead they did the right thing to avoid NPE that would have occurred while unboxing 'null' Integer value. – sactiw Mar 03 '16 at 17:20
  • 7
    @sactiw following this logic, everything on java should have been developed to avoid NPE, which is not the case. Avoid NPE is responsibility of the app developers, not the language internal API's. – Mateus Viccari Jun 20 '16 at 11:44
  • 17
    OMG Why did, simply do not returns NULL, `0` and `NULL` are two big different things – deFreitas Dec 20 '16 at 02:35
  • How irritating! @ryvantage I find myself doing this so much that I just use a wrapper class that accepts a ResultSet in the constructor, and that has its own getInt (etc) methods. – Jacob Crofts Feb 05 '17 at 05:51
  • 2
    @sactiw there is no unboxing if the return parameter is a nullable object (`Integer`). The devs were wrong because `null` and `0` are not the same value. Thus returning `0` when the value is `null` is incorrect (and really messes with newbies to the API writing production code). – ryvantage Feb 05 '17 at 13:01
  • @ryvantage try storing the returned value in a primitive type or compare it with a primitive type or try '<' or '>' comparison with non-primitive type (Integer type) and see? – sactiw Feb 07 '17 at 10:48
  • @sactiw see what? There's nothing to test here. If the API was `getInteger()` and it returned `Integer` then there would be no unboxing and no NPE. There's nothing to test. – ryvantage Feb 07 '17 at 11:58
  • @ryvantage write a simple method like `Integer getInteger()` and return null from it, and see what happens when you do something like: `int c = getInteger();` – sactiw Feb 09 '17 at 05:50
  • 7
    @ryvantage ResultSet is one of the original Java classes, back when Java didn't support auto unboxing. Also it is a very low level API, that shouldn't enforce the potential extra cost of wrapping raw bytes from a database inside a Java Integer object. There is an argument perhaps for adding a getInteger to the interface that returns an Integer (or even Optional). – Richard Feb 09 '17 at 08:34
  • 4
    @sactiw I understand with any nullable object there is a risk of NPE, but there is no risk that the API method itself will throw a NPE. Your argument against `getInteger()` is a general argument against `null` as a construct. But, as long as `null` exists in Java and as long as the database value is `null`, then that is what the method should return. Unarguable: returning `0` when the value is null is incorrect. – ryvantage Feb 09 '17 at 12:02
  • @Richard I understand it's an early API, and getting rid of `getInt()` would be problematic. But yes, adding `getInteger()` would be very wise. They've added to the API before (see: `getBigDecimal()`). – ryvantage Feb 09 '17 at 12:04
  • @ryvantage okay, I got your point, yes having a `getInteger()` method does makes sense, btw, they do provide a general `wasNull()` method that can be used to check if last read column had value NULL. – sactiw Mar 29 '17 at 09:15
  • @sactiw, right. `wasNull()` makes code look so bad, though lol. Also, (this is my main point) it makes the API difficult for unsuspecting newbies who are wondering why their numbers are incorrect. – ryvantage Mar 29 '17 at 11:07
  • I think it is quite wrong that the ResultSet API does work this way. From the domain point of view a value of "0" can be something very different from "NULL". What if 0 is a valid value for a column, where the value can be absent as well. Although this actually seems like very bad database design lol. – skombijohn Mar 29 '17 at 11:42
  • @deFreitas Java primitive types cannot be `null`. It really would've be a great idea to return an `Integer` rather than a primitive `int`. However, I guess the reason they didn't implement this was that JDBC was introduced in JDK 1.1, but autoboxing was introduced in JDK 5. They probably didn't want to upset Java programmers since the programmers were probably already familiar with this strange thing. Also, the Java developers couldn't have added this when JDBC was introduced, since then it would have been too cumbersome to convert between `Integer` and `int`. – Tech Expert Wizard Nov 28 '20 at 14:27
  • not working for me – Matteo Toma May 28 '22 at 09:41
111

Another solution:

public class DaoTools {
    static public Integer getInteger(ResultSet rs, String strColName) throws SQLException {
        int nValue = rs.getInt(strColName);
        return rs.wasNull() ? null : nValue;
    }
}
felipe.zkn
  • 2,012
  • 7
  • 31
  • 63
Patrice IMBERT
  • 1,111
  • 1
  • 7
  • 3
37

Just check if the field is null or not using ResultSet#getObject(). Substitute -1 with whatever null-case value you want.

int foo = resultSet.getObject("foo") != null ? resultSet.getInt("foo") : -1;

Or, if you can guarantee that you use the right DB column type so that ResultSet#getObject() really returns an Integer (and thus not Long, Short or Byte), then you can also just typecast it to an Integer.

Integer foo = (Integer) resultSet.getObject("foo");
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • 2
    No, but it will construct an unnecessary Integer object in the non-null case. (And BTW most JDBC drivers don't hit the db during any ResultSet method calls at all...generally you don't get the ResultSet back until all the data has come over the wire). – EricS Jan 27 '12 at 00:33
  • It depends on fetch size. In most drivers, the default is 10 rows and once the fetch has been retrieved, they will be processed, but the next fetch won't be retrieved until processing has been finished. – Kristo Aun Feb 14 '13 at 07:38
  • 1
    I'm surprise that your answer is not the better answer :-) I vote up because it is the proper answer that avoid the very tricky unsafe wasNull() method. For me it is a supplementary reason to stop using Java ;-) and to continue to use VB.Net where RecordSet has solved this easy problem since more than 10 years ! – schlebe Dec 05 '18 at 13:45
28

I think, it is redundant. rs.getObject("ID_PARENT") should return an Integer object or null, if the column value actually was NULL. So it should even be possible to do something like:

if (rs.next()) {
  Integer idParent = (Integer) rs.getObject("ID_PARENT");
  if (idParent != null) {
    iVal = idParent; // works for Java 1.5+
  } else {
    // handle this case
  }      
}
Andreas Dolk
  • 113,398
  • 19
  • 180
  • 268
  • 6
    Hm, at least in my case, the problem with this is that calling `getObject` doesn't necessarily return an Integer, due to the nature of the column type in the oracle db I'm using ("Number"). – Matt Mc Mar 19 '14 at 23:34
  • Same problem of Matt... With MySQL and `Types.BIGINT` (that should be mapped to a `Long`) the `getObject()` method returns 0 instead of null. – xonya Aug 12 '16 at 07:22
  • 4
    Could also do `rs.getObject("ID_PARENT", Integer.class)` – Arlo Dec 08 '18 at 01:07
11

AFAIK you can simply use

iVal = rs.getInt("ID_PARENT");
if (rs.wasNull()) {
  // do somthing interesting to handle this situation
}

even if it is NULL.

Peter Tillemans
  • 34,983
  • 11
  • 83
  • 114
8

Just an update with Java Generics.

You could create an utility method to retrieve an optional value of any Java type from a given ResultSet, previously casted.

Unfortunately, getObject(columnName, Class) does not return null, but the default value for given Java type, so 2 calls are required

public <T> T getOptionalValue(final ResultSet rs, final String columnName, final Class<T> clazz) throws SQLException {
    final T value = rs.getObject(columnName, clazz);
    return rs.wasNull() ? null : value;
}

In this example, your code could look like below:

final Integer columnValue = getOptionalValue(rs, Integer.class);
if (columnValue == null) {
    //null handling
} else {
    //use int value of columnValue with autoboxing
}

Happy to get feedback

luchoct
  • 419
  • 3
  • 6
  • Interesting. If you're calling `getObject()`, that returns a reference type which can already answer null. The Javadoc at v11 says it answers `null` for SQL `NULL`s. Unless you have an unusual driver or datatype answering some kind of non-null 'empty' objects, I would expect `wasNull()` to only be useful after reading an integer or other primitive type whether the return-type cannot directly represent null. – Thomas W Jun 02 '21 at 03:06
4

You can call this method using the resultSet and the column name having Number type. It will either return the Integer value, or null. There will be no zeros returned for empty value in the database

private Integer getIntWithNullCheck(ResultSet rset, String columnName) {
    try {
        Integer value = rset.getInt(columnName);
        return rset.wasNull() ? null : value;
    } catch (Exception e) {
        return null;
    }
}
  • Can you please go into more detail about how this solves the question? – Sterling Archer Mar 21 '19 at 18:27
  • You can call this method using the resultSet and the column name having Number type. It will either return the Integer value, or null. There will be no zeros returned for empty value in the database. – amine kriaa Mar 22 '19 at 16:32
  • Excellent! Edit that into your answer (and delete the comment after the edit) and you have yourself a good answer :) – Sterling Archer Mar 22 '19 at 16:34
1

For convenience, you can create a wrapper class around ResultSet that returns null values when ResultSet ordinarily would not.

public final class ResultSetWrapper {

    private final ResultSet rs;

    public ResultSetWrapper(ResultSet rs) {
        this.rs = rs;
    }

    public ResultSet getResultSet() {
        return rs;
    }

    public Boolean getBoolean(String label) throws SQLException {
        final boolean b = rs.getBoolean(label);
        if (rs.wasNull()) {
            return null;
        }
        return b;
    }

    public Byte getByte(String label) throws SQLException {
        final byte b = rs.getByte(label);
        if (rs.wasNull()) {
            return null;
        }
        return b;
    }

    // ...

}
Jacob Crofts
  • 176
  • 2
  • 12
1

Just in case someone comes here while programming in Kotlin (as I did), the answer suggested by BalusC works fine. Just be aware that Short and Float are instantiated as Integer and Double (respectively) inside ResultSet, and we should cast them to the correct type after calling getObject(). In my case the final code was:

when {
    propKClass.isSubclassOf(Int::class) -> rs.getObject(colName) as Int? 
    propKClass.isSubclassOf(Short::class) -> (rs.getObject(colName) as Int?)?.toShort()
    propKClass.isSubclassOf(Long::class) -> rs.getObject(colName) as Long?
    propKClass.isSubclassOf(Boolean::class) -> rs.getObject(colName) as Boolean?
    propKClass.isSubclassOf(Double::class) -> rs.getObject(colName) as Double?
    propKClass.isSubclassOf(Float::class) -> (rs.getObject(colName) as Double?)?.toFloat()
    else -> rs.getString(colName)
}
1

If you want an alternative to calling ResultSet.wasNull() you can use getObject() and cast to the correct type.

Long val = (Long)rs.getObject(pos++);

You can also set null values in a Statement with setObject().

pstmt.setObject(pos++, null);
Chad Juliano
  • 1,195
  • 9
  • 4
0

In Kotlin I would just solve it once and be done with the issue forever with this:

fun <K : Any> ResultSet.getNullable(columnLabel: String, type: KClass<K>): K? = 
    this.getObject(columnLabel, type.java)

So later you can just do this:

rs.getNullable("ID_PARENT", Int::class)

I guess if you want you could also do this too

fun <K> ResultSet.getNullable(columnLabel: String, type: Class<K>): K? = 
    this.getObject(columnLabel, type)

So you can just do this:

rs.getNullable("ID_PARENT", Int::class.java)

Or better still make both methods available if you happen to be dealing with developers that can't agree on even the simplest of things.

fun <K : Any> ResultSet.getNullable(columnLabel: String, type: KClass<K>): K? =
        this.getNullable(columnLabel, type.java)

fun <K> ResultSet.getNullable(columnLabel: String, type: Class<K>): K? =
        this.getObject(columnLabel, type)

Edit: if the library is still being fussy you can finally do something like:

rs.getNullable("ID_PARENT", String::class)?.let {FOO.valueOf(it) }
Naruto Sempai
  • 6,233
  • 8
  • 35
  • 51
-1

With java 8 you can do this:

Long nVal = Optional.ofNullable(resultSet.getBigDecimal("col_name"))
                    .map(BigDecimal::longValue).orElse(null));

In that case you ensure that the nVal will be null (and not zero) if the SQL value is NULL

George
  • 7,206
  • 8
  • 33
  • 42
-8

Another nice way of checking, if you have control the SQL, is to add a default value in the query itself for your int column. Then just check for that value.

e.g for an Oracle database, use NVL

SELECT NVL(ID_PARENT, -999) FROM TABLE_NAME;

then check

if (rs.getInt('ID_PARENT') != -999)
{
}

Of course this also is under the assumption that there is a value that wouldn't normally be found in the column.

Chris
  • 1,432
  • 1
  • 10
  • 8
  • 16
    This answer is very likely to cause problems for lots of people. An int column if defined as nullable has a set of values consisting of positive numbers, zero, negative numbers and NULL. At any point in time one can simply insert valid row of data containing this magic number and all of the sudden things will go bad. It's basically the implementation of magic number anti pattern. Don't do this. – Matthias Hryniszak Apr 17 '14 at 11:19