There seems to be some confusion about the roles of the interfaces, I saw some tutorials doing it wrong. The interfaces RowMapper and RowCallbackHandler are responsible for providing a value, resp. producing a side effect, per row.
Therefore, they are not responsible for invoking next() as the framework will take care of advancing to the next row and not invoking the callback, once next() returned false which may happen even when advancing to the first row, in which case the ResultSet is empty. In the latter case, query(… RowMapper<T>) will return an empty List without invoking the RowMapper and query(… RowCallbackHandler) will simply do nothing.
In contrast, ResultSetExtractor is responsible for processing the entire ResultSet, which includes invoking next() appropriately. So your question’s premise (“… each call of extractData gives me new row.”) is wrong. The standard behavior of all JDBC operations is to return a ResultSet being before the first row so that the first next() invocation will either move the current row pointer to the first row or return false if the ResultSet is empty. So if you expect at most one row, your query should rather look like
return jdbcTemplate.query(sql, ps -> {
ps.setLong(1, fooParam1);
ps.setLong(2, fooParam2);
}, rs -> rs.next()? rs.getLong(1): null);
EDIT: This solution tested and works correctly on standard MySQL and Postgresql instances /Andremoniy/.
EDIT-2: This solution absolutely correct and works correctly anywhere. But it should be noticed that there are two different methods in JdbcTemplate class:
public <T> T query(String sql, PreparedStatementSetter pss, ResultSetExtractor<T> rse) - it is used in this example and it returns 0 for rs.getRows() always, indeed. Provided solution is correct for this case.
public void query(String sql, PreparedStatementSetter pss, RowCallbackHandler rch) which has another type of 3rd parameter, and similar code:
jdbcTemplate.query(sql, ps -> {
ps.setLong(1, fooParam1);
ps.setLong(2, fooParam2);
}, rs -> {
System.out.println(rs.getRow());
});
will work differently: rs.getRow() will return number of processing row, and if no rows exist, processRow method will be never invoked (this second method caused my confusing). /Andremoniy/