☕ Java

Supplier

Supplier<T> is a functional interface in java.util.function that represents a factory or source of values — it takes no arguments and returns a value of type T. The single abstract method is T get(). Unlike Function which transforms an input, Supplier produces a value from thin air — from a constant, a computation, a database query, object construction, or any other source. Supplier is the standard abstraction for lazy evaluation (deferring a computation until its result is actually needed), factory methods (providing fresh instances on demand), and dependency injection (providing values to code that doesn't need to know how they were produced). The primitive specializations BooleanSupplier, IntSupplier, LongSupplier, and DoubleSupplier avoid boxing overhead for primitive return types. Supplier is used throughout the JDK: Optional.orElseGet(Supplier), Objects.requireNonNullElseGet(Supplier), Stream.generate(Supplier), Collectors.toCollection(Supplier), and more. This entry covers the Supplier contract and its distinction from other functional interfaces, all primitive specializations, lazy initialization patterns, the distinction between Supplier and Callable, and canonical usage in Optional and Stream.

Supplier<T> — get() and Lazy Evaluation

Supplier<T> is declared as @FunctionalInterface with the single abstract method T get(). There is no andThen, compose, or negate — Supplier has no default composition methods. Its sole purpose is to produce a value on demand. The value can be the same object every time (a constant supplier), a new object each time (a factory supplier), or a computationally expensive value deferred until needed (a lazy supplier). The defining property of Supplier is laziness by nature. Passing a Supplier to a method defers the computation until get() is called. This is in contrast to passing the value itself — a value is computed eagerly (before the call), while a Supplier is a thunk (a deferred computation). The method receiving the Supplier decides whether and when to call get(). If the value is not needed (a conditional branch is not taken, an Optional is already present, a cache hit occurs), get() is never called and the computation is never performed. Supplier imposes no contract on get() beyond returning T. A Supplier may: return null (though many APIs that accept Supplier document that null is not permitted), throw an unchecked exception, return the same value on every call, return a different value on every call, or have side effects. When a Supplier should be able to throw a checked exception, the standard Supplier<T> cannot be used directly — the lambda would need to catch the checked exception internally. The java.util.concurrent.Callable<V> interface serves the same purpose but allows checked exceptions from its call() method. Common Supplier patterns: () -> new SomeObject() (factory — new instance on each get()), () -> CONSTANT (constant — same object on each get()), () -> expensiveCompute() (deferred computation — computed once when get() is called), () -> { if (cached == null) cached = compute(); return cached; } (memoizing supplier — computed once, cached thereafter).
Java
// ── Basic Supplier lambda ─────────────────────────────────────────────
import java.util.function.*;

Supplier<String>  greeting  = () -> "Hello, World!";
Supplier<List<String>> listFactory = () -> new ArrayList<>();
Supplier<Double>  random    = Math::random;   // method reference as Supplier
Supplier<LocalDate> today   = LocalDate::now; // static method reference

System.out.println(greeting.get());    // Hello, World!
System.out.println(listFactory.get()); // [] — new ArrayList on each call
System.out.println(random.get());      // random double, different each call
System.out.println(today.get());       // today's date

// ── Factory Supplier: new instance on each get() ──────────────────────
Supplier<StringBuilder> sbFactory = StringBuilder::new;  // constructor reference

StringBuilder sb1 = sbFactory.get();  // new StringBuilder
StringBuilder sb2 = sbFactory.get();  // another new StringBuilder
System.out.println(sb1 == sb2);       // false — different instances

// ── Constant Supplier: same value every time ──────────────────────────
String sharedValue = "shared";
Supplier<String> constant = () -> sharedValue;
System.out.println(constant.get() == constant.get());  // true — same object

// ── Lazy computation: deferred until get() is called ──────────────────
Supplier<String> expensive = () -> {
    System.out.println("Computing...");
    return performExpensiveComputation();   // only runs when get() is called
};
// At this point, "Computing..." has NOT been printed

System.out.println("Before get()");
String value = expensive.get();    // NOW the computation runs
System.out.println("Got: " + value);
// Before get()
// Computing...
// Got: <result>

// ── Memoizing Supplier: compute once, cache thereafter ────────────────
public class Lazy<T> {
    private final Supplier<T>  delegate;
    private volatile T         cached;
    private volatile boolean   initialized = false;

    public Lazy(Supplier<T> delegate) { this.delegate = delegate; }

    public T get() {
        if (!initialized) {
            synchronized (this) {
                if (!initialized) {
                    cached = delegate.get();
                    initialized = true;
                }
            }
        }
        return cached;
    }
}

Lazy<ExpensiveObject> lazyObj = new Lazy<>(() -> new ExpensiveObject());
// ExpensiveObject not created yet
ExpensiveObject obj1 = lazyObj.get();  // created here
ExpensiveObject obj2 = lazyObj.get();  // returned from cache
System.out.println(obj1 == obj2);      // true — same instance

// ── Supplier vs value argument: lazy vs eager ─────────────────────────
// Eager: errorMessage() is ALWAYS evaluated, even if no error:
void logIfError(boolean error, String message) { if (error) log(message); }
logIfError(false, expensiveMessage());  // expensiveMessage() runs even though error=false

// Lazy: supplier only evaluated when actually needed:
void logIfError(boolean error, Supplier<String> message) { if (error) log(message.get()); }
logIfError(false, () -> expensiveMessage());  // expensiveMessage() NOT called

Primitive Specializations and Standard Library Usage

The four primitive Supplier specializations avoid boxing overhead for their return types. BooleanSupplier returns boolean from getAsBoolean(). IntSupplier returns int from getAsInt(). LongSupplier returns long from getAsLong(). DoubleSupplier returns double from getAsDouble(). These are used when the produced value is a primitive and boxing would add unnecessary allocation overhead — particularly in hot paths or numerical computation. Optional.orElseGet(Supplier<? extends T> supplier) is the canonical Supplier usage in the JDK. It calls supplier.get() only if the Optional is empty — the supplier provides the fallback value lazily. This is in contrast to Optional.orElse(T other), which evaluates other eagerly even if the Optional is present. For computationally expensive or side-effectful defaults (database queries, network calls, object construction), orElseGet is always the correct choice. Stream.generate(Supplier<? extends T> s) creates an infinite sequential stream where each element is provided by s.get(). The stream is lazy — elements are generated only when the terminal operation requests them. Combined with Stream.limit(), it provides a concise way to create streams of any size. Common uses: Stream.generate(() -> new Object()) for infinite streams of new objects, Stream.generate(Math::random) for random number streams, Stream.generate(Queue::poll) to drain a queue as a stream. Collectors.toCollection(Supplier<C> collectionFactory) allows specifying a custom collection implementation as the target of a collect() operation. Instead of Collectors.toList() (which may produce any List implementation), Collectors.toCollection(() -> new LinkedList<>()) guarantees the specific collection type. Objects.requireNonNullElseGet(T obj, Supplier<? extends T> supplier) (Java 9+) returns obj if non-null, otherwise calls supplier.get(). Like Optional.orElseGet, the supplier is only evaluated when obj is null.
Java
// ── Primitive Suppliers: no boxing ───────────────────────────────────
IntSupplier    counter  = new AtomicInteger(0)::getAndIncrement;  // method ref
LongSupplier   clock    = System::currentTimeMillis;
DoubleSupplier pi       = () -> Math.PI;
BooleanSupplier hasMore = queue::isEmpty;

System.out.println(counter.getAsInt());    // 0
System.out.println(counter.getAsInt());    // 1
System.out.println(clock.getAsLong());     // current time millis
System.out.println(pi.getAsDouble());      // 3.141592653589793

// ── Optional.orElseGet vs orElse: lazy vs eager ───────────────────────
Optional<String> present = Optional.of("Alice");
Optional<String> empty   = Optional.empty();

// orElse: default ALWAYS evaluated:
String r1 = present.orElse(expensiveDefault());    // expensiveDefault() RUNS — wasteful
String r2 = empty.orElse(expensiveDefault());      // expensiveDefault() RUNS — needed

// orElseGet: default evaluated ONLY when Optional is empty:
String r3 = present.orElseGet(() -> expensiveDefault());  // NOT called — Alice returned
String r4 = empty.orElseGet(() -> expensiveDefault());    // called — needed

// Rule: prefer orElseGet for non-trivial defaults:
Optional<User> user = userCache.get(id);
String email = user
    .map(User::email)
    .orElseGet(() -> database.fetchUserEmail(id));  // DB query only if not cached

// ── Stream.generate: infinite stream from Supplier ────────────────────
// Random numbers:
Stream.generate(Math::random)
    .limit(5)
    .forEach(d -> System.out.printf("%.4f%n", d));

// Unique IDs:
AtomicLong idGen = new AtomicLong(1000);
Stream.generate(idGen::getAndIncrement)
    .limit(5)
    .forEach(System.out::println);  // 1000, 1001, 1002, 1003, 1004

// Default object instances:
Stream.generate(HashMap<String, Integer>::new)
    .limit(3)
    .forEach(map -> map.put("key", 42));  // three separate HashMaps

// ── Collectors.toCollection: specific collection type ─────────────────
List<String> names = List.of("Charlie", "Alice", "Bob");

// sorted TreeSet via Supplier:
TreeSet<String> sorted = names.stream()
    .collect(Collectors.toCollection(TreeSet::new));
System.out.println(sorted);   // [Alice, Bob, Charlie]

// LinkedList via Supplier:
LinkedList<String> linked = names.stream()
    .collect(Collectors.toCollection(LinkedList::new));

// PriorityQueue via Supplier:
PriorityQueue<String> pq = names.stream()
    .collect(Collectors.toCollection(PriorityQueue::new));

// ── Supplier for dependency injection and configuration ───────────────
public class ReportGenerator {
    private final Supplier<Connection> connectionSupplier;

    // Dependency injected as Supplier — caller controls how connection is obtained:
    public ReportGenerator(Supplier<Connection> connectionSupplier) {
        this.connectionSupplier = connectionSupplier;
    }

    public Report generate() {
        try (Connection conn = connectionSupplier.get()) {  // get fresh connection
            return queryAndBuild(conn);
        }
    }
}

// Test with in-memory connection:
ReportGenerator testGen = new ReportGenerator(() -> h2Connection);
// Production with pool:
ReportGenerator prodGen  = new ReportGenerator(() -> connectionPool.acquire());

// ── Supplier vs Callable: checked exceptions ──────────────────────────
// Supplier<T>: get() does NOT declare checked exceptions
// Callable<V>: call() DOES declare throws Exception

Supplier<String> supplier = () -> {
    try {
        return Files.readString(Path.of("file.txt"));  // IOException must be caught
    } catch (IOException e) {
        throw new UncheckedIOException(e);  // wrap checked as unchecked
    }
};

Callable<String> callable = () -> Files.readString(Path.of("file.txt"));
// callable.call() can throw IOException — caller handles it
// Use Callable when the caller needs to handle checked exceptions

Related Topics in Java 8 Features

Lambda Expressions
Lambda expressions, introduced in Java 8, are anonymous functions — blocks of code that can be stored in variables, passed as arguments, and returned from methods, treating behavior as data. A lambda has three parts: a parameter list, an arrow token (->), and a body. The body is either a single expression (whose value is the implicit return value) or a block of statements wrapped in braces. Lambdas implement functional interfaces — interfaces with exactly one abstract method — allowing any lambda whose signature matches the abstract method's signature to be used wherever that interface is expected. The lambda syntax is syntactic sugar: every lambda is compiled to an invocation of the functional interface's abstract method, with the compiler generating a class (via invokedynamic) that implements the interface and delegates to the lambda body. This entry covers the complete lambda syntax including all shorthand forms, variable capture and the effectively-final constraint, method references as a specialized lambda syntax, the relationship between lambdas and the type system, how lambdas interact with exception handling, the invokedynamic compilation strategy and its performance characteristics, and the complete set of rules governing lambda type inference.
Functional Interfaces
A functional interface is any Java interface that has exactly one abstract method. This single-abstract-method (SAM) contract makes the interface a valid target type for a lambda expression or method reference — the lambda provides the implementation of that one abstract method. The @FunctionalInterface annotation is optional but strongly recommended: it causes the compiler to verify that the interface satisfies the SAM constraint, rejecting it at compile time if there is more than one abstract method. The java.util.function package, introduced in Java 8, provides 43 standard functional interfaces organized around four root types — Function, Consumer, Supplier, Predicate — and their variations for primitives (IntFunction, LongSupplier, DoubleConsumer, etc.), binary operations (BiFunction, BiConsumer, BiPredicate), and unary operators (UnaryOperator, IntUnaryOperator, etc.). This entry covers the design principles behind functional interfaces, the complete @FunctionalInterface contract including default and static methods, the full java.util.function hierarchy and the pattern that governs naming, creating custom functional interfaces with checked exceptions, composing functional interfaces via default methods, and the relationship between functional interfaces and the type system including the rules for lambda assignment and widening.
Predicate
Predicate<T> is a functional interface in java.util.function representing a boolean-valued function of one argument, with the single abstract method boolean test(T t). It is one of the four foundational functional interfaces in the Java standard library and is used throughout the Collections framework, Streams API, and Optional for filtering, condition testing, and validation. Predicate is designed for composition: its default methods and(Predicate), or(Predicate), and negate() allow building complex boolean expressions from simple predicates without boilerplate. The static methods isEqual(Object) and not(Predicate) provide factory methods for common cases. The primitive specializations IntPredicate, LongPredicate, and DoublePredicate avoid boxing overhead for numeric values. BiPredicate<T,U> extends the concept to two-argument boolean functions. This entry covers the complete Predicate API, all composition methods and their short-circuit semantics, the static factory methods, primitive specializations, BiPredicate, using Predicate in stream pipelines and Collections methods, building validation frameworks with Predicate composition, and the performance and readability trade-offs of different composition styles.
Function
Function<T,R> is a functional interface in java.util.function representing a function that accepts one argument of type T and produces a result of type R, with the single abstract method R apply(T t). It is the most general transformation interface in the standard library, used throughout the Streams API for mapping (Stream.map()), in Optional for value transformation (Optional.map(), Optional.flatMap()), and as a building block for more specialized functional interfaces. Function provides two default composition methods — andThen() and compose() — that create new functions by chaining two functions together, enabling functional pipeline construction without intermediate variables. The specializations cover all combinations of generic and primitive inputs and outputs: ToIntFunction, IntFunction, IntToLongFunction, and so on. UnaryOperator<T> extends Function<T,T> for operations that transform a value within the same type. BiFunction<T,U,R> generalizes to two input arguments. This entry covers the complete Function API, the semantics of andThen versus compose, all specializations and when each is appropriate, the functional relationship between Function and other java.util.function types, partial application patterns, and Function as the basis for building data pipelines.