Difference between orElse(other) and orElseGet(supplier)
5 min read
NOTE
The core difference between .orElse() and .orElseGet() comes down to Performance and Evaluation - Eager vs. Lazy
orElse(…)
evaluates EAGERLY
The default object inside the parentheses is always instantiated, even if the Optional contains a value and the default is never used.
orElseGet(…)
evaluates LAZILY
The lambda expression/supplier is executed only if the Optional is empty.
Direct Technical Comparison
Feature
orElse(...)
orElseGet(...)
Input Type
A raw object instance (T)
A functional Supplier (Supplier<? extends T>)
Evaluation Time
Always executed immediately (Eager)
Executed only if the container is empty (Lazy)
Memory/CPU Overhead
High if creating the fallback object is heavy
Zero if the Optional already has a value
Real-World Production Examples
Scenario 1
NOTE
Use orElse() when the fallback is a constant or free to create==
If your fallback value is a hardcoded string, a primitive, or a pre-existing static object, use orElse(). Creating these takes negligible CPU cycles, so eager evaluation doesn’t hurt performance.
// PRODUCTION EXAMPLE: HTTP Header Checkpublic String getTrackingId(HttpServletRequest request) { return Optional.ofNullable(request.getHeader("X-Tracking-ID")) .orElse("NOT_SET"); // 🟢 Good: "NOT_SET" is a cheap string literal. }
Scenario 2
NOTE
Use orElseGet() when the fallback requires a Heavy Operation
If your default value requires allocating a complex object, making a database query, or calling an external service, never use orElse().
The Anti-Pattern (orElse in Production)
// DO NOT DO THISpublic User getUserSettings(String userId) { return userRepository.findById(userId) .orElse(fallbackDatabaseService.fetchGlobalSystemDefaults()); }
This breaks production. How ?
Even if userRepository.findById(userId) successfully finds a user 99% of the time, your application will still execute fetchGlobalSystemDefaults() and call the database every single time this method runs. This causes massive, unnecessary database traffic.
The Fix (orElseGet in Production)
// DO THIS INSTEADpublic User getUserSettings(String userId) { return userRepository.findById(userId) .orElseGet(() -> fallbackDatabaseService.fetchGlobalSystemDefaults()); }
Why this is safe
The database query inside orElseGet is wrapped inside a lazy lambda. It will only trigger if the user isn’t found in the first repository.
Applying this to a simple StringBuilder Code
In your specific snippet, the difference manifests like this:
// 1. Eager: A new StringBuilder memory allocation occurs EVERY time this line executesStringBuilder sb2 = sbo.orElse(new StringBuilder("Not Present"));// 2. Lazy: A new StringBuilder allocation occurs ONLY if sbo is emptyStringBuilder sb3 = sbo.orElseGet(() -> new StringBuilder("Not Present"));
If this code executes inside a tight loop processing millions of transactions, orElse will create millions of garbage StringBuilder instances that immediately need to be cleaned up by the Garbage Collector. orElseGet avoids this entire memory overhead.
Fallback patterns with Optional
Best practices to throw custom API responses, or initializing default configurations/beans on startup
Approach 1: Throwing Custom API Responses
In a modern production application (like Spring Boot), you rarely want to return a fallback object like "Not Present" directly to the client.
Instead, if data is missing,
- you should interrupt execution and throw a distinct, meaningful exception that an automated global exception handler can catch and convert into a clean HTTP status code (like 404 Not Found).
Production Implementation - .orElseThrow
Instead of orElse or orElseGet, use .orElseThrow().
This uses a lazy evaluation supplier pattern to ensure the exception object is only created if data is actually missing.
@RestController@RequestMapping("/api/users")public class UserController { @Autowired private UserRepository userRepository; @GetMapping("/{id}") public ResponseEntity<UserResponse> getUserById(@PathVariable String id) { User user = userRepository.findById(id) // 🟢 BEST PRACTICE: Throws lazily only if the user is missing .orElseThrow(() -> new ResourceNotFoundException("User with ID " + id + " not found")); return ResponseEntity.ok(convertToDto(user)); }}
Where to throw exceptions ? in controller or in the service layer ?
.findById(accountId)
.orElseThrow(() -> new AccountNotFoundException("Account not found: " + accountId));
}
Standard is to throw it in service layer and sometimes in controller, if needed. Check [[1. Exception handling in Rest]]
Supporting Global Exception Handler
To prevent raw stack traces from leaking to the frontend, map your custom exception to a clean API response using an @ControllerAdvice component:
@ControllerAdvicepublic class GlobalExceptionHandler { @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<ErrorDetails> handleNotFound(ResourceNotFoundException ex, WebRequest request) { ErrorDetails errorDetails = new ErrorDetails( LocalDateTime.now(), ex.getMessage(), request.getDescription(false) ); return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND); // 404 Status }}
Approach 2: Initializing Default Configurations or Beans
When configuring your application on startup, you often look for
Environment variables or External configuration values
If they aren’t provided, you fall back to a safe default config
Production Implementation
For system configurations, .orElse() is often preferred if your default fallback is a pre-allocated constant or a lightweight placeholder.
However, if building the fallback setup involves parsing files or initializing network blocks, use .orElseGet().
@Configurationpublic class ThirdPartyApiConfig { // Pre-allocated static fallback configuration bean (Thread-Safe & Immutable) private static final ApiClientSettings DEFAULT_SETTINGS = new ApiClientSettings("https://company.com", 5000); @Bean public ApiClient apiClient(Environment env) { return Optional.ofNullable(env.getProperty("api.custom.url")) // Transform the string URL into a fully operational ApiClient if present .map(customUrl -> new ApiClient(customUrl, 10000)) // 🟢 BEST PRACTICE: Use orElse() because DEFAULT_SETTINGS is a pre-allocated static constant. // No new memory allocations occur here on lookup failure. .orElse(DEFAULT_SETTINGS); }}
What if the default configuration requires a database check?
If your default configuration isn’t a hardcoded constant and must be fetched from an external source dynamically, pivot immediately to .orElseGet() to prevent loading it on every bean verification cycle:
@Beanpublic AppConfig dynamicConfig(ConfigRepository repo) { return Optional.ofNullable(System.getenv("APP_ENV_CONFIG")) .map(json -> parseConfigJson(json)) // 🟢 BEST PRACTICE: Only queries the DB if the environment variable is blank .orElseGet(() -> repo.findActiveSystemDefault()); }
Summary
Rule of Thumb
orElseThrow()
Use this for Business Logic & API Controllers when missing data means the request cannot proceed.
orElse(STATIC_CONSTANT)
Use this for Configurations when your fallback is an immutable, pre-constructed object that costs nothing to read.
orElseGet(() -> expensiveCall())
Use this everywhere else when your fallback requires computing something new (new StringBuilder(), database reads, or object hydration).
Use Optional.map() and Optional.flatMap() to securely transform data fieldsbefore applying these fallback rules