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
- Use for Return Values: Use Optional for methods that might return null
- Don’t Overuse: Don’t use Optional for every nullable field
- Prefer Transform: Use transform() instead of manual if/else
- Chain Operations: Chain transformations for cleaner code
- Provide Defaults: Always provide sensible defaults with or()
Preconditions Best Practices
- Validate Early: Check parameters at the beginning of methods
- Clear Messages: Provide descriptive error messages
- Use Appropriate Methods: Choose the right precondition method
- Fail Fast: Catch errors early in the execution
- Consistent Validation: Apply consistent validation across your codebase
Common Pitfalls
Optional Pitfalls
- Overusing Optional: Don’t use Optional for every nullable field
- Ignoring Absent Values: Always handle the case when Optional is absent
- Nested Optionals: Avoid Optional<Optional
> - use flatMap instead - Performance: Optional has minimal overhead but don’t use in tight loops
Preconditions Pitfalls
- Inconsistent Validation: Apply validation consistently across your codebase
- Poor Error Messages: Write clear, descriptive error messages
- Missing Validation: Don’t forget to validate important parameters
- Over-validation: Don’t validate everything - focus on critical parameters
Performance Considerations
- Optional Overhead: Minimal overhead, but consider in performance-critical code
- Preconditions Overhead: Very low overhead, safe to use liberally
- String Formatting: Preconditions with format strings have minimal cost
- 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