Hot questions for Handling Exception in Guava

Top 10 Java Open Source / Guava / Handling Exception

Java: null pointer exception when unboxing Integer?

Question: This code is causing a null pointer exception. I have no idea why:

private void setSiblings(PhylogenyTree node, Color color) throws InvalidCellNumberException {
    PhylogenyTree parent = node.getParent();

    for (PhylogenyTree sibling : parent.getChildren()) {
        if (! sibling.equals(node)) {
            Animal animal = sibling.getAnimal();
            BiMap<PhylogenyTree, Integer> inverse = cellInfo.inverse();
            int cell = inverse.get(animal); // null pointer exception here
            setCellColor(cell, color);
        }
    }
}

I've examined it in the debugger, and all the local variables are non-null. How else could this be happening? The BiMap is from Google Collections.

Answer: The null pointer exception is a result of unboxing the result of inverse.get(animal). If inverse doesn't contain the key animal, it returns null, "of type" Integer. Given that the assignment is to an int reference, Java unboxes the value into an int, resulting in a null pointer exception.

You should either check for inverse.containsKey(animal) or use Integer as the local variable type to avoid unboxing and act accordingly. The proper mechanism depends on your context.


Guava cache and preserving checked exceptions

Question: I'm refactoring some code to use guava Cache.

Initial code:

public Post getPost(Integer key) throws SQLException, IOException {
    return PostsDB.findPostByID(key);
}

In order not to break something I need to preserve any thrown exception as is, without wrapping it.

Current solution appears somewhat ugly:

public Post getPost(final Integer key) throws SQLException, IOException {
    try {
        return cache.get(key, new Callable<Post>() {
            @Override
            public Post call() throws Exception {
                return PostsDB.findPostByID(key);
            }
        });
    } catch (ExecutionException e) {
        Throwable cause = e.getCause();
        if (cause instanceof SQLException) {
            throw (SQLException) cause;
        } else if (cause instanceof IOException) {
            throw (IOException) cause;
        } else if (cause instanceof RuntimeException) {
            throw (RuntimeException) cause;
        } else if (cause instanceof Error) {
            throw (Error) cause;
        } else {
            throw new IllegalStateException(e);
        }
    }
}

Is there any possible way to make it nicer?

Answer: Just after writing the question started thinking about utility method powered with generics. Then remembered something about Throwables.

It may also be necessary to handle UncheckedExecutionException or even ExecutionError.

So the solution is:

public Post getPost(final Integer key) throws SQLException, IOException {
    try {
        return cache.get(key, new Callable<Post>() {
            @Override
            public Post call() throws Exception {
                return PostsDB.findPostByID(key);
            }
        });
    } catch (ExecutionException e) {
        Throwables.propagateIfPossible(
            e.getCause(), SQLException.class, IOException.class);
        throw new IllegalStateException(e);
    } catch (UncheckedExecutionException e) {
        Throwables.throwIfUnchecked(e.getCause());
        throw new IllegalStateException(e);
    }
}

Iterables.find and Iterators.find - instead of throwing exception, get null

Question: I'm using google-collections and trying to find the first element that satisfies Predicate if not, return me 'null'.

Unfortunately, Iterables.find and Iterators.find throws NoSuchElementException when no element is found.

Now, I am forced to do

Object found = null;
if ( Iterators.any( newIterator(...) , my_predicate )
{
    found = Iterators.find( newIterator(...), my_predicate )
}

I can surround by 'try/catch' and do the same thing but for my use-cases, I am going to encounter many cases where no-element is found.

Is there a simpler way of doing this?

Answer: Since Guava 7, you can do this using the Iterables.find() overload that takes a default value:

Iterables.find(iterable, predicate, null);

Guava MultiMap and ConcurrentModificationException

Question: I don't understand why I get a ConcurrentModificationException when I iterate through this multimap. I read the following entry, but I am not sure if I understood the whole thing. I tried to add a synchronized block. But my doubt is what to synchronize with, and when.

The multimap is a field and created like this:

private Multimap<GenericEvent, Command> eventMultiMap =   
   Multimaps.synchronizedMultimap(HashMultimap.<GenericEvent, Command> create());

and used like this :

eventMultiMap.put(event, command);

and like this ( I tried to synchronize this part on the map, but without success )

for (Entry<GenericEvent, Command> entry : eventMultiMap.entries()) {
    if (entry.getValue().equals(command)) {
        eventMultiMap.remove(entry.getKey(), entry.getValue());
        nbRemoved++;
    }
}

Answer: Calling remove on a collection while you're iterating through it will cause a ConcurrentModificationException every time, even if it's all done in the same thread - the Right Thing to do is get an explicit iterator and call .remove() on that.

Modifying your example:

Iterator<Map.Entry<GenericEvent, Command>> i = eventMultiMap.entries().iterator();
while (i.hasNext()) {
    if (i.next().getValue().equals(command)) {
        i.remove();
        nbRemoved++;
    }
}

In java8 you can also use a lambda approach:

eventMultiMap.entries().removeIf(genericEventCommandEntry -> genericEventCommandEntry.getValue().equals(command));

Error:com.android.builder.dexing.DexArchiveBuilderException: Failed to process guava-21.0.jar Android 3.1 Dev Channel

Question: After updating my dependency versions in AndroidStudio3.1, I started getting the following error:

Information:Gradle tasks [:app:assembleDebug]
Error:com.android.builder.dexing.DexArchiveBuilderException: Failed to process C:\Users\Blabla\.gradle\caches\modules-2\files-2.1\com.google.guava\guava\21.0\3a3d111be1be1b745edfa7d91678a12d7ed38709\guava-21.0.jar
Error:com.android.builder.dexing.DexArchiveBuilderException: Error while dexing.
Error:com.android.tools.r8.ApiLevelException: Default interface methods are only supported starting with Android N (--min-api 24): java.util.Collection com.google.common.collect.BiMap.values()
Error:Execution failed for task ':app:transformClassesWithDexBuilderForDebug'.

I have already cleaned and rebuilt project. Checked my "multiDexEnabled true" and 'compile 'com.android.support:multidex:1.0.2' is added.

What else can I do?

Answer: Update your Guava version, Guava 21 is Java 8 only.

Update your gradle to Guava version 27.0.1-android, which is compatible with Android:

Gradle 4.6+:

dependencies {
  implementation 'com.google.guava:guava:27.0.1-android'
}

Previous Gradle versions:

dependencies {
  compile 'com.google.guava:guava:27.0.1-android'
}