Everything Java

← Back to blog

Published on Thu Apr 10 2025 12:00:00 GMT+0000 (Coordinated Universal Time) by Purusothaman Ramanujam

Google Guava Optional and Preconditions: Safe Null Handling and Validation

Introduction

Google Guava provides two essential utilities for writing more robust Java code: Optional for safe null handling and Preconditions for parameter validation. These utilities help prevent null pointer exceptions and make your code more defensive.

In this post, we’ll explore how to use Guava’s Optional and Preconditions effectively.

Adding Guava to Your Project

Maven Dependency

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>

Gradle Dependency

implementation 'com.google.guava:guava:32.1.3-jre'

Optional

Guava’s Optional provides a way to handle null values more safely. It’s similar to Java 8’s Optional but with some additional features:

import com.google.common.base.Optional;
public class OptionalExample {
public static void main(String[] args) {
// Create Optional with a value
Optional<String> present = Optional.of("hello");
System.out.println(present.isPresent()); // true
System.out.println(present.get()); // "hello"
// Create Optional with no value
Optional<String> absent = Optional.absent();
System.out.println(absent.isPresent()); // false
// Safe get with default value
String value = absent.or("default");
System.out.println(value); // "default"
// Transform if present
Optional<Integer> length = present.transform(String::length);
System.out.println(length.get()); // 5
// Chain operations
Optional<String> result = present
.transform(String::toUpperCase)
.transform(s -> s + " WORLD");
System.out.println(result.get()); // "HELLO WORLD"
}
}

Optional Creation Methods

public class OptionalCreationExample {
public static void main(String[] args) {
// From non-null value
Optional<String> present = Optional.of("value");
// From potentially null value
String nullableString = getNullableString();
Optional<String> fromNullable = Optional.fromNullable(nullableString);
// Absent (no value)
Optional<String> absent = Optional.absent();
// From Java 8 Optional
java.util.Optional<String> java8Optional = java.util.Optional.of("value");
Optional<String> guavaOptional = Optional.fromJavaUtil(java8Optional);
}
private static String getNullableString() {
return Math.random() > 0.5 ? "value" : null;
}
}

Optional Operations

public class OptionalOperationsExample {
public static void main(String[] args) {
Optional<String> optional = Optional.of("hello");
// Check if present
if (optional.isPresent()) {
System.out.println("Value: " + optional.get());
}
// Or with default
String value = optional.or("default");
// Or with supplier
String valueFromSupplier = optional.or(() -> "computed default");
// Transform if present
Optional<Integer> length = optional.transform(String::length);
// Chain transformations
Optional<String> result = optional
.transform(String::toUpperCase)
.transform(s -> s + " WORLD");
// Or with another Optional
Optional<String> fallback = Optional.of("fallback");
String finalValue = optional.or(fallback);
}
}

Practical Optional Examples

public class OptionalPracticalExample {
public static class User {
private final String name;
private final String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() { return name; }
public String getEmail() { return email; }
}
public static class UserService {
private final Map<String, User> users = new HashMap<>();
public Optional<User> findUser(String id) {
return Optional.fromNullable(users.get(id));
}
public String getUserEmail(String id) {
return findUser(id)
.transform(User::getEmail)
.or("No email found");
}
public String getUserDisplayName(String id) {
return findUser(id)
.transform(user -> user.getName() + " (" + user.getEmail() + ")")
.or("User not found");
}
}
public static void main(String[] args) {
UserService service = new UserService();
service.users.put("1", new User("John", "john@example.com"));
System.out.println(service.getUserEmail("1")); // "john@example.com"
System.out.println(service.getUserEmail("2")); // "No email found"
System.out.println(service.getUserDisplayName("1")); // "John (john@example.com)"
}
}

Preconditions

Guava’s Preconditions help with parameter validation and provide clear error messages:

import com.google.common.base.Preconditions;
public class PreconditionsExample {
public static void divide(int a, int b) {
// Check if b is not zero
Preconditions.checkArgument(b != 0, "Divisor cannot be zero");
// Check if a is positive
Preconditions.checkArgument(a > 0, "Dividend must be positive, got %s", a);
double result = (double) a / b;
System.out.println("Result: " + result);
}
public static void processUser(String name, Integer age) {
// Check for null
Preconditions.checkNotNull(name, "Name cannot be null");
Preconditions.checkNotNull(age, "Age cannot be null");
// Check state
Preconditions.checkState(age >= 0, "Age must be non-negative, got %s", age);
System.out.println("Processing user: " + name + ", age: " + age);
}
public static void main(String[] args) {
try {
divide(10, 2); // Works fine
divide(10, 0); // Throws IllegalArgumentException
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
try {
processUser("John", 25); // Works fine
processUser(null, 25); // Throws NullPointerException
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}

Preconditions Methods

public class PreconditionsMethodsExample {
public static void main(String[] args) {
String name = "John";
Integer age = 25;
List<String> list = Arrays.asList("a", "b", "c");
// checkArgument - validates arguments
Preconditions.checkArgument(age > 0, "Age must be positive");
Preconditions.checkArgument(name != null && !name.isEmpty(), "Name cannot be null or empty");
// checkNotNull - validates non-null
Preconditions.checkNotNull(name, "Name cannot be null");
Preconditions.checkNotNull(age, "Age cannot be null");
// checkState - validates object state
Preconditions.checkState(age >= 18, "User must be 18 or older");
// checkElementIndex - validates array/list index
Preconditions.checkElementIndex(1, list.size(), "Index out of bounds");
// checkPositionIndex - validates position for insertion
Preconditions.checkPositionIndex(2, list.size(), "Position out of bounds");
// checkPositionIndexes - validates range
Preconditions.checkPositionIndexes(0, 2, list.size());
}
}

Advanced Preconditions

public class AdvancedPreconditionsExample {
public static class User {
private String name;
private int age;
private boolean active;
public User(String name, int age) {
this.name = Preconditions.checkNotNull(name, "Name cannot be null");
this.age = Preconditions.checkArgument(age >= 0, "Age must be non-negative, got %s", age);
this.active = true;
}
public void setAge(int age) {
Preconditions.checkArgument(age >= 0, "Age must be non-negative, got %s", age);
this.age = age;
}
public void activate() {
Preconditions.checkState(!active, "User is already active");
this.active = true;
}
public void deactivate() {
Preconditions.checkState(active, "User is already inactive");
this.active = false;
}
public String getName() { return name; }
public int getAge() { return age; }
public boolean isActive() { return active; }
}
public static void main(String[] args) {
try {
User user = new User("John", 25);
System.out.println("User created: " + user.getName());
user.setAge(30);
user.deactivate();
user.activate();
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}

Collection Validation

public class CollectionValidationExample {
public static <T> List<T> getElements(List<T> list, int start, int end) {
Preconditions.checkNotNull(list, "List cannot be null");
Preconditions.checkElementIndex(start, list.size(), "Start index out of bounds");
Preconditions.checkElementIndex(end - 1, list.size(), "End index out of bounds");
Preconditions.checkArgument(start <= end, "Start index must be <= end index");
return list.subList(start, end);
}
public static <T> T getElement(List<T> list, int index) {
Preconditions.checkNotNull(list, "List cannot be null");
Preconditions.checkElementIndex(index, list.size(), "Index out of bounds");
return list.get(index);
}
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c", "d");
try {
List<String> subList = getElements(list, 1, 3);
System.out.println(subList); // [b, c]
String element = getElement(list, 2);
System.out.println(element); // "c"
// This will throw an exception
getElement(list, 5);
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}

Best Practices

Optional Best Practices

  1. Use for Return Values: Use Optional for methods that might return null
  2. Don’t Overuse: Don’t use Optional for every nullable field
  3. Prefer Transform: Use transform() instead of manual if/else
  4. Chain Operations: Chain transformations for cleaner code
  5. Provide Defaults: Always provide sensible defaults with or()

Preconditions Best Practices

  1. Validate Early: Check parameters at the beginning of methods
  2. Clear Messages: Provide descriptive error messages
  3. Use Appropriate Methods: Choose the right precondition method
  4. Fail Fast: Catch errors early in the execution
  5. Consistent Validation: Apply consistent validation across your codebase

Common Pitfalls

Optional Pitfalls

  1. Overusing Optional: Don’t use Optional for every nullable field
  2. Ignoring Absent Values: Always handle the case when Optional is absent
  3. Nested Optionals: Avoid Optional<Optional> - use flatMap instead
  4. Performance: Optional has minimal overhead but don’t use in tight loops

Preconditions Pitfalls

  1. Inconsistent Validation: Apply validation consistently across your codebase
  2. Poor Error Messages: Write clear, descriptive error messages
  3. Missing Validation: Don’t forget to validate important parameters
  4. Over-validation: Don’t validate everything - focus on critical parameters

Performance Considerations

  1. Optional Overhead: Minimal overhead, but consider in performance-critical code
  2. Preconditions Overhead: Very low overhead, safe to use liberally
  3. String Formatting: Preconditions with format strings have minimal cost
  4. Exception Creation: Exceptions are expensive, but validation prevents worse issues

Conclusion

Guava’s Optional and Preconditions provide powerful tools for writing more robust Java code. Optional helps handle null values safely, while Preconditions ensures proper parameter validation.

Use Optional for return values that might be null, and use Preconditions to validate parameters early in your methods. These utilities can significantly improve your code’s reliability and maintainability.

Remember to use these utilities judiciously - Optional is best for return values, and Preconditions should be used for critical parameter validation. With proper usage, these utilities can help prevent many common runtime errors.

Resources

Written by Purusothaman Ramanujam

← Back to blog