Google Guava provides extended collections, caching, string processing, and utility classes that fill gaps in the Java standard library.

Setup

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

Immutable Collections

Thread-safe, compact, fail-fast on modification:

  ImmutableList<String> list = ImmutableList.of("a", "b", "c");
ImmutableSet<String> set = ImmutableSet.of("x", "y", "z");
ImmutableMap<String, Integer> map = ImmutableMap.of("one", 1, "two", 2);

// Builder for larger collections
ImmutableMap<String, User> users = ImmutableMap.<String, User>builder()
    .put("alice", aliceUser)
    .put("bob", bobUser)
    .build();
  

Prefer over Collections.unmodifiable*() — truly immutable, not just unmodifiable view.

Multimap

One key → multiple values:

  Multimap<String, String> multimap = ArrayListMultimap.create();
multimap.put("fruit", "apple");
multimap.put("fruit", "banana");
multimap.put("vegetable", "carrot");

Collection<String> fruits = multimap.get("fruit");  // [apple, banana]
  

Variants: ArrayListMultimap, HashMultimap, LinkedHashMultimap.

BiMap

Bidirectional map — value must be unique:

  BiMap<String, Integer> biMap = HashBiMap.create();
biMap.put("one", 1);
biMap.put("two", 2);

biMap.inverse().get(1);  // "one"
  

Table

Two-key map (row × column):

  Table<String, String, Integer> sales = HashBasedTable.create();
sales.put("Q1", "Product A", 1000);
sales.put("Q1", "Product B", 1500);
sales.put("Q2", "Product A", 1200);

Integer q1ProductA = sales.get("Q1", "Product A");  // 1000
Map<String, Integer> q1Row = sales.row("Q1");
  

String Utilities

  // Joiner
String result = Joiner.on(", ").skipNulls().join("a", null, "b");  // "a, b"

// Splitter
Iterable<String> parts = Splitter.on(',').trimResults().omitEmptyStrings()
    .split("a, b, , c");  // ["a", "b", "c"]

// Strings
Strings.isNullOrEmpty(s);       // null or ""
Strings.nullToEmpty(s);         // null → ""
Strings.emptyToNull(s);         // "" → null

// Case format conversion
CaseFormat.LOWER_HYPEN.to(CaseFormat.LOWER_CAMEL, "foo-bar");  // "fooBar"
  

Preconditions and Optional

  // Argument validation
Preconditions.checkNotNull(user, "User must not be null");
Preconditions.checkArgument(age > 0, "Age must be positive, got %s", age);
Preconditions.checkState(isReady, "Service not ready");

// Optional utilities
Optional<User> opt = Optional.fromNullable(userRepository.findById(id));
User user = opt.or(userRepository.findByEmail(email));
  

Cache (Legacy — prefer Caffeine)

  LoadingCache<String, User> cache = CacheBuilder.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .recordStats()
    .build(new CacheLoader<String, User>() {
        @Override
        public User load(String key) {
            return userRepository.findByEmail(key).orElseThrow();
        }
    });
  

Rate Limiter

  RateLimiter limiter = RateLimiter.create(10.0);  // 10 permits per second

public void handleRequest() {
    if (limiter.tryAcquire(1, Duration.ofMillis(100))) {
        processRequest();
    } else {
        throw new RateLimitExceededException();
    }
}
  

EventBus

Simple publish-subscribe within a JVM:

  EventBus eventBus = new EventBus();

eventBus.register(new Object() {
    @Subscribe
    public void handleOrderCreated(OrderCreatedEvent event) {
        sendConfirmationEmail(event);
    }
});

eventBus.post(new OrderCreatedEvent(orderId));
  

For cross-JVM events, use Kafka or RabbitMQ instead.

Best Practices

  • Use immutable collections for shared, read-only data
  • Prefer Caffeine over Guava Cache for new projects
  • Use Preconditions for fast-fail argument validation
  • Guava RateLimiter is useful for in-process throttling
  • Keep Guava updated — it’s widely used and well-maintained