RadhaPublication
HomeInterviewLearning PathsRole TracksMockBooksPricing
Login / Sign UpLog in

© 2026 Radha Publication. All rights reserved.

Shop on AmazonDonateContactPrivacyTerms
Java questions
MediumJava#566 min readJul 11, 2026

Supplier interface.

practice
learning
Practice modeTest yourself instead of reading straight through

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.'

🧠 Memory Map
Memory map — visual summary of this topic

What it is

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.

How it works under the hood

  1. The compiler sees a lambda or method reference and a target type of Supplier<T>.
  2. It checks that the code matches the get() shape: no inputs, one return value.
  3. Captured local variables must be effectively final, meaning you do not change them after the lambda captures them.
  4. At runtime, the JVM creates a small callable object for the lambda. The exact mechanics use invokedynamic and synthetic implementation code, but the mental model is simply 'an object that can be asked for a value later'.
  5. When some framework or method calls get(), the supplier's body runs right then, so the cost is deferred until that moment.

When and why to use it

  • Lazy defaults: use it when the fallback value is expensive, such as a database lookup or object construction.
  • Value generation: use it for IDs, timestamps, random numbers, test data, or fresh objects.
  • Framework callbacks: APIs such as Optional.orElseGet, Stream.generate, and CompletableFuture.supplyAsync accept a Supplier because they need a value later, not now.

Common comparisons

InterfaceInputOutputTypical use
SupplierNoneOne valueLazy creation
FunctionOne valueOne valueTransform data
ConsumerOne valueNoneSide effects
PredicateOne valuetrue/falseFilter or test
CallableNoneOne valueTask 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.

MethodEvaluationBest for
orElseEagerCheap defaults
orElseGetLazyExpensive defaults

Performance and edge cases

  • The interface itself is tiny; the overhead of calling get() is usually negligible, often just a few nanoseconds beyond normal method-call cost.
  • The real cost is whatever the supplier does. If get() hits a database or remote API, the work is dominated by I/O, not the lambda.
  • Primitive specializations such as 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.
  • It is not automatically cached or thread-safe. If you call get() twice, you may get two different values, and if the body mutates shared state, you must synchronize that yourself.
  • For 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.

Java
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:

  • How is 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.
  • When would you choose 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.
  • Can a 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.
  • Is a 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.
  • What happens with 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.
  • Does calling 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.
  • Is 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.
  • Can a Supplier return 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.
  • Is a Supplier thread-safe? Not automatically. Thread safety depends on the code inside get(), especially if it mutates shared state or lazily initializes a resource.

Common Mistakes:

  • Using Supplier when you need input. Correction: if the logic needs a value from the caller, choose Function or another interface with parameters.
  • Using orElse for expensive defaults. Correction: if the fallback does real work, use orElseGet so that work is skipped when possible.
  • Assuming get() is cached. Correction: a Supplier is just a function; if you want memoization, you must build caching yourself.
  • Ignoring primitive specializations. Correction: in performance-sensitive code, prefer 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.
  • Best for lazy defaults, factories, and deferred work.
  • orElseGet is lazy; orElse is eager.
  • Stateful suppliers can return different values on each call.
  • Use primitive suppliers to avoid boxing in hot loops.
  • Not automatically thread-safe, cached, or exception-friendly for checked exceptions.

Practice Tasks:

  • Write a Supplier<String> that returns the current time as text and call it three times.
  • Refactor an Optional.orElse(...) call to orElseGet(...) and prove that the fallback stops running eagerly.
  • Create a memoized Supplier that computes a value once and reuses it on later calls.
Previous
Back to Questions←→to navigate
Next

Recommended Resources

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

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()); } } }