Search

Software Engineer's Notes

Tag

Integer

OptionalInt vs Optional in Java: When to Use Which (and Why)

If you’ve worked with Java’s Optional<T>, you’ve probably also seen OptionalInt, OptionalLong, and OptionalDouble. Why does Java have both Optional<Integer> and OptionalInt? Which should you choose—and when?

This guide breaks it down with clear examples and a simple decision checklist.

  • Optional<Integer> is the generic Optional for reference types. It’s flexible, works everywhere generics are needed, but boxes the int (adds memory & CPU overhead).
  • OptionalInt is a primitive specialization for int. It avoids boxing, is faster and lighter, and integrates nicely with IntStream, but is less flexible (no generics, fewer methods).

Use OptionalInt inside performance-sensitive code and with primitive streams; use Optional<Integer> when APIs require Optional<T> or you need a uniform type.

What Are They?

Optional<Integer>

A container that may or may not hold an Integer value:

Optional<Integer> maybeCount = Optional.of(42);     // present
Optional<Integer> emptyCount = Optional.empty();    // absent

OptionalInt

A container specialized for the primitive int:

OptionalInt maybeCount = OptionalInt.of(42);     // present
OptionalInt emptyCount = OptionalInt.empty();    // absent

Both types model “a value might be missing” without using null.

Why Do We Have Two Types?

  1. Performance vs. Flexibility
    • Optional<Integer> requires boxing (intInteger). This allocates objects and adds GC pressure.
    • OptionalInt stores the primitive directly—no boxing.
  2. Stream Ecosystem
    • Primitive streams (IntStream, LongStream, DoubleStream) return primitive optionals (OptionalInt, etc.) for terminal ops like max(), min(), average().

Key Differences at a Glance

AspectOptional<Integer>OptionalInt
TypeGeneric Optional<T>Primitive specialization (int)
BoxingYes (Integer)No
Interop with IntStreamIndirect (must box/unbox)Direct (IntStream.max()OptionalInt)
Methodsget(), map, flatMap, orElse, orElseGet, orElseThrow, ifPresentOrElse, etc.getAsInt(), orElse, orElseGet, orElseThrow, ifPresentOrElse, stream(); no generic map (use primitive ops)
Use in generic APIsYes (fits Optional<T>)No (type is fixed to int)
Memory/CPUHigher (boxing/GC)Lower (no boxing)

How to Choose (Quick Decision Tree)

  1. Are you working with IntStream / primitive stream results?
    Use OptionalInt.
  2. Do you need to pass the result through APIs that expect Optional<T> (e.g., repository/service interfaces, generic utilities)?
    Use Optional<Integer>.
  3. Is this code hot/performance-sensitive (tight loops, high volume)?
    → Prefer OptionalInt to avoid boxing.
  4. Do you need to “map” the contained value using generic lambdas?
    Optional<Integer> (richer map/flatMap).
    (With OptionalInt, use primitive operations or convert to Optional<Integer> when necessary.)

Common Usage Examples

With Streams (Primitive Path)

int[] nums = {1, 5, 2};
OptionalInt max = IntStream.of(nums).max();

int top = max.orElse(-1);          // -1 if empty
max.ifPresent(m -> System.out.println("Max: " + m));

With Collections / Generic APIs

List<Integer> ages = List.of(18, 21, 16);
Optional<Integer> firstAdult =
    ages.stream().filter(a -> a >= 18).findFirst();  // Optional<Integer>

int age = firstAdult.orElseThrow(); // throws if empty

Converting Between Them (when needed)

OptionalInt oi = OptionalInt.of(10);
Optional<Integer> o = oi.isPresent() ? Optional.of(oi.getAsInt()) : Optional.empty();

Optional<Integer> og = Optional.of(20);
OptionalInt op = og.isPresent() ? OptionalInt.of(og.get()) : OptionalInt.empty();

Benefits of Using Optional (Either Form)

  • Eliminates fragile null contracts: Callers are forced to handle absence.
  • Self-documenting APIs: Return type communicates “might not exist.”
  • Safer refactoring: Missing values become compile-time-visible.

Extra Benefit of OptionalInt

  • Performance: No boxing/unboxing. Less GC. Better fit for numeric pipelines.

When to Use Them

Good fits:

  • Return types where absence is valid (e.g., findById, max, “maybe present” queries).
  • Stream terminal results (IntStreamOptionalInt).
  • Public APIs where you want to make “might be empty” explicit.

Avoid or be cautious:

  • Fields in entities/DTOs: Prefer plain fields with domain defaults; Optional fields complicate serialization and frameworks.
  • Method parameters: Usually model “optional input” with method overloading or builders, not Optional parameters.
  • Collections of Optional: Prefer filtering to keep collections of concrete values.
  • Overuse in internal code paths where a simple sentinel (like -1) is a clear domain default.

Practical Patterns

Pattern: Prefer Domain Defaults for Internals

If your domain has a natural default (e.g., “unknown count” = 0), returning int may be simpler than OptionalInt.

Pattern: Optional for Query-Like APIs

When a value may not be found and absence is legitimate, return an Optional:

OptionalInt findWinningScore(Game g) { ... }

Pattern: Keep Boundaries Clean

  • At primitive stream boundariesOptionalInt.
  • At generic/service boundariesOptional<Integer>.

Pitfalls & Tips

  • Don’t call get()/getAsInt() without checking. Prefer orElse, orElseGet, orElseThrow, or ifPresentOrElse.
  • Consider readability. If every call site immediately does orElse(-1), a plain int with a documented default may be clearer.
  • Measure before optimizing. Choose OptionalInt for hot paths, but don’t prematurely micro-optimize.

Cheatsheet

  • Need performance + primitives? OptionalInt
  • Need generic compatibility or richer ops? Optional<Integer>
  • Returning from IntStream ops? OptionalInt
  • Public service/repo interfaces? Often Optional<Integer>
  • Don’t use as fields/parameters/inside collections (usually).

Mini Examples: Correct vs. Avoid

Good (return type)

public OptionalInt findTopScore(UserId id) { ... }

Avoid (parameter)

// Hard to use and read
public void updateScore(OptionalInt maybeScore) { ... }

Prefer overloads or builder/setter methods.

Avoid (entity field)

class Player {
  OptionalInt age; // complicates frameworks/serialization
}

Prefer int age with a domain default or a nullable wrapper managed at the edges.

Conclusion

  • Use OptionalInt when you’re in the primitive/stream world or performance matters.
  • Use Optional<Integer> when you need generality, compatibility with Optional<T> APIs, or richer functional methods.
  • Keep Optionals at API boundaries, not sprinkled through fields and parameters.

Pick the one that keeps your code clear, fast, and explicit about absence.

int vs Integer in Java: What They Are, Why Both Exist, and How to Choose

Quick Decision Guide

  • Use int for primitive number crunching, counters, loops, and performance-critical code.
  • Use Integer when you need null, work with collections/generics/Streams, use it as a map key, or interact with APIs that require objects.

What Are int and Integer?

  • int is a primitive 32-bit signed integer type.
    • Range: −2,147,483,648 to 2,147,483,647.
    • Stored directly on the stack or inside objects as a raw value.
  • Integer is the wrapper class for int (in java.lang).
    • An immutable object that contains an int value (or can be null).
    • Provides methods like compare, parseInt (static in Integer), etc.

Why Do We Have Two Types?

Java was designed with primitives for performance and memory efficiency. Later, Java introduced generics, Collections, and object-oriented APIs that need reference types. Wrapper classes (like Integer) bridge primitives and object APIs, enabling features primitives can’t provide (e.g., nullability, method parameters of type Object, use as generic type arguments).

Key Differences at a Glance

Aspectint (primitive)Integer (wrapper class)
NullabilityCannot be nullCan be null
Memory4 bytes for the valueObject header + 4 bytes value (+ padding)
PerformanceFast (no allocation)Slower (allocation, GC, boxing/unboxing)
Generics/CollectionsNot allowed as type parameterAllowed: List<Integer>
Default value (fields)0null
Equality== compares values== compares references; use .equals() for value
AutoboxingNot applicableWorks with int via autoboxing/unboxing
MethodsN/AUtility & instance methods (compareTo, hashCode, etc.)

Autoboxing & Unboxing (and the Gotchas)

Java will automatically convert between int and Integer:

Integer a = 5;    // autoboxing: int -> Integer
int b = a;        // unboxing: Integer -> int

Pitfall: Unboxing a null Integer throws NullPointerException.

Integer maybeNull = null;
int x = maybeNull; // NPE at runtime!

Tip: When a value can be absent, prefer OptionalInt/Optional<Integer> or check for null before unboxing.

Integer Caching (−128 to 127)

Integer.valueOf(int) caches values in [−128, 127]. This can make some small values appear identical by reference:

Integer x = 100;
Integer y = 100;
System.out.println(x == y);      // true (same cached object)
System.out.println(x.equals(y)); // true

Integer p = 1000;
Integer q = 1000;
System.out.println(p == q);      // false (different objects)
System.out.println(p.equals(q)); // true

Rule: Always use .equals() for value comparison with wrappers.

When to Use int

  • Counters, indices, arithmetic in tight loops.
  • Performance-critical code paths to avoid allocation/GC.
  • Fields that are always present (never absent) and don’t need object semantics.
  • Switch statements and bit-level operations.

Example:

int sum = 0;
for (int i = 0; i < nums.length; i++) {
  sum += nums[i];
}

When to Use Integer

  • Collections/Generics/Streams require reference types:
List<Integer> scores = List.of(10, 20, 30);

  • Nullable numeric fields (e.g., optional DB columns, partially populated DTOs).
  • Map keys or values where object semantics and equals/hashCode matter:
Map<Integer, String> userById = new HashMap<>();

  • APIs that expect Object or reflection/serialization frameworks.

Benefits of Each

Benefits of int

  • Speed & low memory footprint.
  • No NullPointerException from unboxing.
  • Straightforward arithmetic.

Benefits of Integer

  • Nullability to represent “unknown/missing”.
  • Works with Collections, Generics, Streams.
  • Provides utility methods and can be used in APIs requiring objects.
  • Immutability makes it safe as a map key.

When Not to Use Them

  • Avoid Integer in hot loops or large arrays where performance/memory is critical (boxing creates many objects).
    • Prefer int[] over List<Integer> when possible.
  • Avoid int when a value might be absent or needs to live in a collection or generic API.
  • Beware of unboxing nulls—if a value can be null, don’t immediately unbox to int.

Practical Patterns

1) DTO with Optional Field

class ProductDto {
  private Integer discountPercent; // can be null if no discount
  // getters/setters
}

2) Streams: Primitive vs Boxed

int sum = IntStream.of(1, 2, 3).sum();          // primitive stream: fast
int sum2 = List.of(1, 2, 3).stream()
                 .mapToInt(Integer::intValue)
                 .sum();                         // boxed -> primitive

3) Safe Handling of Nullable Integer

Integer maybe = fetchCount();           // might be null
int count = (maybe != null) ? maybe : 0; // avoid NPE

4) Overloads & Method Selection

If you provide both:

void setValue(int v) { /* ... */ }
void setValue(Integer v) { /* ... */ }

  • Passing a literal (setValue(5)) picks int.
  • Passing null only compiles for Integer (setValue(null)).

Common FAQs

Q: Why does List<int> not compile?
A: Generics require reference types; use List<Integer> or int[].

Q: Why does x == y sometimes work for small Integers?
A: Because of Integer caching (−128 to 127). Don’t rely on it—use .equals().

Q: I need performance but also collections—what can I do?
A: Use primitive arrays (int[]) or primitive streams (IntStream) to compute, then convert minimally when you must interact with object-based APIs.

Cheat Sheet

  • Performance: int > Integer
  • Nullability: Integer
  • Collections/Generics: Integer
  • Equality: int uses ==; Integer use .equals() for values
  • Hot loops / big data: prefer int / int[]
  • Optional numeric: Integer or OptionalInt (for primitives)

Mini Example: Mixing Both Correctly

class Scoreboard {
  private final Map<Integer, String> playerById = new HashMap<>(); // needs Integer
  private int totalScore = 0;                                      // fast primitive

  void addScore(int playerId, int score) {
    totalScore += score; // primitive math
    playerById.put(playerId, "Player-" + playerId);
  }

  Integer findPlayer(Integer playerId) {
    // Accepts null safely; returns null if absent
    return (playerId == null) ? null : playerId;
  }
}

Final Guidance

  • Default to int for computation and tight loops.
  • Choose Integer for nullability and object-centric APIs (Collections, Generics, frameworks).
  • Watch for NPE from unboxing and avoid boxing churn in performance-sensitive code.
  • Use .equals() for comparing Integer values; not ==.

Blog at WordPress.com.

Up ↑