DEV Community

Cover image for Surfing with FP Java - Mastering Supplier<T>
André Borba
André Borba

Posted on

Surfing with FP Java - Mastering Supplier<T>

Introduction

In the last episode, we mastered Consumer, the functional interface for performing actions.

Now, let’s turn our focus to Supplier, the simplest yet most powerful interface for lazy value generation.

If Predicate decides, Function transforms, and Consumer acts, then Supplier is the source. It provides values on demand, without requiring input.

What Is Supplier?

Here’s the definition:

@FunctionalInterface
public interface Supplier<T> {
    T get();
}
Enter fullscreen mode Exit fullscreen mode
  • Input: none.
  • Output: an object of type T.
  • Purpose: generate or supply values, often lazily or repeatedly.

Why Use Supplier?

Traditionally, values are produced eagerly:

String token = UUID.randomUUID().toString();
Enter fullscreen mode Exit fullscreen mode

With Supplier, we can defer execution and encapsulate value creation:

Supplier<String> tokenSupplier = () -> UUID.randomUUID().toString();

System.out.println(tokenSupplier.get()); // A new token each call
Enter fullscreen mode Exit fullscreen mode

This is especially powerful in lazy evaluation, configuration loading, testing, and dependency injection.

Practical Examples

1. Random or Unique Values

Suppliers naturally express randomness or unique generation:

Supplier<Integer> randomInt = () -> new Random().nextInt(100);

System.out.println(randomInt.get()); // e.g., 42
System.out.println(randomInt.get()); // e.g., 17
Enter fullscreen mode Exit fullscreen mode

2. Deferred Initialization

Delay expensive computation until it’s needed:

Supplier<List<User>> heavyQuery = () -> database.fetchAllUsers();

// Nothing happens yet
List<User> users = heavyQuery.get(); // Query executes only here
Enter fullscreen mode Exit fullscreen mode

3. Factories and Object Creation

Suppliers are elegant factories:

Supplier<User> newUser = () -> new User(UUID.randomUUID().toString());

User user1 = newUser.get();
User user2 = newUser.get();
Enter fullscreen mode Exit fullscreen mode

4. Stream Integration

Suppliers work with Stream.generate to produce infinite sequences:

Supplier<String> uuidSupplier = () -> UUID.randomUUID().toString();

List<String> ids = Stream.generate(uuidSupplier)
    .limit(3)
    .toList();

System.out.println(ids); 
// [550e8400-e29b-41d4-a716-446655440000, ...]
Enter fullscreen mode Exit fullscreen mode

Real-World Patterns

  1. Configuration Providers:
    Load config lazily when requested.

  2. Caching with Lazy Evaluation:
    Compute once, then reuse.

  3. Dependency Injection:
    Pass Suppliers instead of concrete objects for flexible object creation.

  4. Testing and Mocking:
    Suppliers make it easy to inject fake or mock data.

Best Practices

  • Keep Suppliers Pure: Avoid hidden side effects. Suppliers should primarily generate values.
  • Name Clearly: Use names like tokenSupplier, userFactory, configProvider.
  • Combine with Higher-Order Functions: Suppliers can be passed to methods to delay execution or customize behavior.

Common Pitfalls

  • Uncontrolled Infinite Streams: Stream.generate without a limit leads to infinite loops.
  • Heavy Logic Inside: Don’t hide complex operations inside Supplier#get(), it should be lightweight and predictable.
  • Side Effects: While possible, side-effect-driven suppliers (e.g., network calls) blur responsibilities. Prefer purity.

Functional Analogy

Think of Supplier as a well:

  • You don’t provide anything to it.
  • Each time you draw (get()), it gives you fresh water (T).
  • The well might be deep, infinite, or cached, but it always supplies.

Conclusion

Supplier is the lazy generator of functional Java.
It encapsulates value creation, supports lazy evaluation, and integrates seamlessly with streams, factories, and testing strategies.

By mastering Supplier, you unlock on-demand computation and flexible object creation.

What’s Next

In the next episode, we’ll dive into UnaryOperator, a specialized function for single-input transformations.
Where Function transforms from one type to another, UnaryOperator specializes in transforming within the same type, a cornerstone of functional composition.

Top comments (7)

Collapse
 
ygritte profile image
ygritte •

Você tem o French OCaml no seu repositório do GitHub.
Livre-se dele, ou a Fada Escocesa virá te visitar.
Huuuuhuhuhhu... que medooo!

Na sgrìobh OCaml, mas e do thoil e.
Chan eil duine sam bith a' sgrìobhadh OCaml ach na Frangaich.

Collapse
 
borba profile image
André Borba •

Pepapepa pepa pe papa!!

Collapse
 
ygritte profile image
ygritte •

Off-topic: Why is there a russian version also for one of the things on gh? You expat?

Thread Thread
 
borba profile image
André Borba •

Haha, nope! I'm from Brazil. I just like Russian and decided to keep a Russian version there as well. :)

Thread Thread
 
ygritte profile image
ygritte •

@highflyer910 I don't know what to make of this guy. Seems like though fate wants something with him. Clojure Russian writing person from Brazil doing Design Patterns in Java.... sort of like a Tracy-Loki hibrid. Don't look at the PERL though. It was probably just a phase like Green Day. Sergio Mendes side is cool, but that Martin Fowler corpo thing is in there too. Hmh. Idk. You should keep him on the radar. Btw there's The Expanse S01 stuff with OCaml too. Weird, huh?

Thread Thread
 
highflyer910 profile image
Thea •

Clojure, Java patterns, and writing in Russian from Brazil... that's not just a developer, that's a walking plot twist :D Okay, I'll keep him on the radar 👀
And I'll pretend I didn't see the Perl :D

Thread Thread
 
borba profile image
André Borba •

ROFL! I just read this entire thread and I'm fucking dying 🤣

Okay, when you put it like that, I sound absolutely unhinged.

I swear there's a perfectly reasonable explanation for all of this.
I just don't know what it is yet.

😂😂😂

Fuck... Now I have to delete Perl from my history.