Hook: Think of a Supplier like a vending machine: you press get() and it gives you a value, but you never hand it any input.
Question: What is the Supplier interface in Java 8?
Answer: Supplier<T> is a functional interface from java.util.function with one abstract method, get(), that returns a value and takes no arguments. It is used when you want to delay creating a value until it is actually needed. A common use is supplying defaults, generating random values, or creating objects lazily.
Interview-Ready Answer: 'In Java 8, Supplier<T> is a functional interface that represents a no-input, return-only action. I use it when I want lazy evaluation: for example, Optional.orElseGet, Stream.generate, or CompletableFuture.supplyAsync. The key idea is that get() is called only when the value is actually needed, which avoids unnecessary work and can prevent wasted database calls or expensive object creation.'
Detailed Explanation: Supplier<T> is one of Java 8's functional interfaces, which means it has exactly one abstract method and can therefore be implemented with a lambda expression or method reference. Its method is simple: T get(). No parameters, no checked-exception contract, just return a value.
Supplier<T>.get() shape: no inputs, one return value.invokedynamic and synthetic implementation code, but the mental model is simply 'an object that can be asked for a value later'.get(), the supplier's body runs right then, so the cost is deferred until that moment.Optional.orElseGet, Stream.generate, and CompletableFuture.supplyAsync accept a Supplier because they need a value later, not now.| Interface | Input | Output | Typical use |
|---|---|---|---|
Supplier | None | One value | Lazy creation |
Function | One value | One value | Transform data |
Consumer | One value | None | Side effects |
Predicate | One value | true/false | Filter or test |
Callable | None | One value | Task that may throw checked exceptions |
Another classic comparison is Optional.orElse versus Optional.orElseGet. orElse evaluates its argument immediately, even when the optional already has a value. orElseGet takes a Supplier and calls it only when the optional is empty.
| Method | Evaluation | Best for |
|---|---|---|
orElse | Eager | Cheap defaults |
orElseGet | Lazy | Expensive defaults |
get() is usually negligible, often just a few nanoseconds beyond normal method-call cost.get() hits a database or remote API, the work is dominated by I/O, not the lambda.IntSupplier, LongSupplier, DoubleSupplier, and BooleanSupplier avoid boxing, which can save allocations in tight loops.Supplier may return null, but many APIs do not expect it, so treat null carefully.get() twice, you may get two different values, and if the body mutates shared state, you must synchronize that yourself.CompletableFuture.supplyAsync, the supplier runs on the common pool by default; on a blocking I/O task, that can starve the pool. On a 4-core machine, the common pool is typically small, so use a custom executor for slow network work.Memory note: The best mental model is 'no-input vending machine': you do not hand it data, you just ask for one value later.
Real-World Example: Imagine a checkout service in an e-commerce app. It uses a Supplier<BigDecimal> for a fallback shipping price, because the fallback is expensive and should only run if the cached rate is missing. The team also uses a Supplier to create request IDs only when a request reaches the audit logger.
What goes wrong? A developer replaces orElseGet with orElse while refactoring. Suddenly, the fallback shipping-rate supplier runs on every request, even when the cache already has the answer. In production, p95 latency jumps from around 80 ms to 600-900 ms, Redis or database logs show repeated reads, connection pools get saturated, and users see the checkout spinner hang before occasional 502 errors. The bug is subtle because the code still 'works'; it just wastes work at scale.
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class SupplierDemo {
public static void main(String[] args) {
AtomicInteger expensiveCalls = new AtomicInteger();
// This Supplier models an expensive fallback: it should only run when needed.
Supplier<String> expensiveFallback = () -> {
int call = expensiveCalls.incrementAndGet();
System.out.println("Computing fallback value... call #" + call);
return "generated-value-" + call;
};
Optional<String> present = Optional.of("cached-value");
// Lazy: the Supplier is NOT called because the Optional already has a value.
String fromPresent = present.orElseGet(expensiveFallback);
System.out.println("orElseGet with present Optional -> " + fromPresent);
System.out.println("Supplier calls so far -> " + expensiveCalls.get());
// Eager: the Supplier runs immediately, even though the Optional already has a value.
String eager = present.orElse(expensiveFallback.get());
System.out.println("orElse with present Optional -> " + eager);
System.out.println("Supplier calls so far -> " + expensiveCalls.get());
Optional<String> empty = Optional.empty();
String fromEmpty = empty.orElseGet(expensiveFallback);
System.out.println("orElseGet with empty Optional -> " + fromEmpty);
System.out.println("Supplier calls so far -> " + expensiveCalls.get());
// A Supplier can be stateful: each call may return a different value.
Supplier<Integer> diceRoll = () -> ThreadLocalRandom.current().nextInt(1, 7);
System.out.println("Two dice rolls from the same Supplier -> " + diceRoll.get() + ", " + diceRoll.get());
// Stream.generate repeatedly asks the Supplier for more values, but only as many as needed.
System.out.print("Five generated numbers -> ");
Stream.generate(diceRoll).limit(5).forEach(n -> System.out.print(n + " "));
System.out.println();
// Failure path: a Supplier may throw a runtime exception from get().
Supplier<String> failingSupplier = () -> {
throw new IllegalStateException("Config has not been loaded yet");
};
try {
System.out.println(failingSupplier.get());
} catch (RuntimeException ex) {
System.out.println("Failure path -> " + ex.getClass().getSimpleName() + ": " + ex.getMessage());
}
}
}Follow-up & Tricky Questions:
Supplier different from Function? A Supplier takes no input and just returns a value, while a Function accepts one input and transforms it into one output.IntSupplier instead of Supplier<Integer>? Use IntSupplier when you are producing lots of primitive ints in hot code, because it avoids boxing overhead and can reduce garbage.Supplier throw a checked exception? Not directly, because get() does not declare checked exceptions. If you need that behavior, you usually wrap the exception in a runtime exception or use a different abstraction.Supplier lazy by itself? No. A Supplier is only a description of how to obtain a value later; it becomes lazy only when some caller chooses to invoke get() later.CompletableFuture.supplyAsync? The supplier is executed asynchronously and its return value becomes the future's result. If the supplier blocks on I/O, use a custom executor instead of the common pool.get() twice always return the same object? No. A Supplier may create a fresh object every time, or it may return a cached one; the interface does not promise either behavior.orElseGet always better than orElse? No. orElseGet is better only when the fallback is expensive or has side effects. If the fallback is a simple constant, orElse is perfectly fine.null? Yes, the type allows it, but many APIs become fragile when they receive null, so it is safer to return a real value or wrap it in Optional where appropriate.get(), especially if it mutates shared state or lazily initializes a resource.Common Mistakes:
Supplier when you need input. Correction: if the logic needs a value from the caller, choose Function or another interface with parameters.orElse for expensive defaults. Correction: if the fallback does real work, use orElseGet so that work is skipped when possible.get() is cached. Correction: a Supplier is just a function; if you want memoization, you must build caching yourself.IntSupplier, LongSupplier, or DoubleSupplier to avoid boxing.Memory Hook: 'Supplier = vending machine.' No input goes in, one item comes out, and every press can produce a fresh item.
Cheat Sheet:
Supplier<T> = one method, get(), no arguments, returns T.orElseGet is lazy; orElse is eager.Practice Tasks:
Supplier<String> that returns the current time as text and call it three times.Optional.orElse(...) call to orElseGet(...) and prove that the fallback stops running eagerly.