Back to Blog
Java

java int vs integer: Choosing the Right Type

java int vs integer: Understand the practical differences between Java's int primitive and Integer wrapper: memory, performance, nullability, equality, and when to use...

Javaprimitive typeswrapper classesautoboxingperformancenull safety
Diagram comparing a primitive int box and an Integer object box, highlighting value vs reference storage

The question of java int vs integer comes up constantly in Java codebases. The answer depends on whether you need a primitive value or an object reference. int is a primitive type that stores a 32-bit signed integer directly. Integer is a wrapper class that encapsulates an int value inside an object. That difference affects memory, performance, null handling, equality checks, and how values behave in collections and generic APIs.

The Fundamental Difference: Primitive vs Reference Type

An int variable holds the actual numeric value. An Integer variable holds a reference to an object that contains the numeric value. This is not just a syntactic distinction; it changes what you can do with the variable.

int primitive = 42; Integer wrapped = Integer.valueOf(42);

primitive is a stack-allocated value. wrapped is a heap-allocated object reference. When you assign one int to another, you copy the value. When you assign one Integer to another, you copy the reference, so both variables point to the same object.

This distinction becomes visible when you compare values. Two Integer objects with the same numeric value are not necessarily equal with ==, because == compares references, not values. The same is not true for int.

Autoboxing and Unboxing: Where the Two Meet

Java automatically converts between int and Integer in many contexts. This feature, called autoboxing and unboxing, hides the conversion but does not eliminate its cost.

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

Autoboxing uses Integer.valueOf() internally, which caches values between -128 and 127. For values outside that range, a new Integer object is created each time. Unboxing calls intValue() on the wrapper.

This conversion is convenient, but it introduces overhead. Every autoboxing operation allocates an object (except for cached values). Every unboxing operation performs a method call. In tight loops or high-throughput code, that overhead can accumulate.

Memory and Performance Implications

An int occupies 4 bytes. An Integer object typically occupies 16 bytes or more, depending on the JVM and whether compressed references are enabled. That is a 4x difference before considering the reference itself. When you store Integer values in a collection, each element is a reference to a separate object, so the memory footprint grows further.

Performance follows the same pattern. Arithmetic on int values is direct CPU operations. Arithmetic on Integer values requires unboxing first, then the operation, and often re-boxing afterward. The JIT compiler can sometimes eliminate this overhead through escape analysis, but not always, especially when objects escape the current method.

Consider a simple sum loop:

int sum = 0; for (int i = 0; i < 1_000_000; i++) { sum += i; }

Using Integer for sum would require unboxing and re-boxing on every iteration, producing many temporary objects. The JIT may optimize some of this away, but relying on that is risky. For numeric computation, prefer int unless you have a specific reason to use Integer.

Nullability and Optionality: When Integer Is Required

An int variable cannot be null. It always has a numeric value, defaulting to 0 if not explicitly initialized. An Integer variable can be null, which makes it useful when a value may be absent.

Integer maybeNull = null; // valid int cannotBeNull = null; // compile error

This nullability is the primary reason to use Integer in domain models, database entities, or API parameters where a field might not have a value. For example, a Person object might have an age field that is unknown. Using Integer lets you represent that with null instead of a sentinel value like -1.

However, nullability introduces a risk. If you unbox a null Integer, the JVM throws a NullPointerException.

Integer value = null; int result = value + 1; // NullPointerException at runtime

This is a common source of runtime failures. When you use Integer, always check for null before unboxing, or use methods like Objects.requireNonNull or Optional to handle absence explicitly.

Collections and Generics: Why Integer Is Necessary

Java generics do not work with primitive types. You cannot write List<int> or Map<String, int>. The type parameter must be a reference type, so you must use Integer.

List<Integer> numbers = new ArrayList<>(); numbers.add(10); // autoboxing int first = numbers.get(0); // unboxing

This is the most common reason developers encounter Integer in practice. Every List<Integer>, Set<Integer>, or Map<String, Integer> requires autoboxing when adding primitive values and unboxing when retrieving them.

The performance cost of collections is inherent to the design. If you need to store many numeric values and performance is critical, consider specialized libraries like Trove or use arrays (int[]) instead of List<Integer>. For most applications, the convenience of collections outweighs the overhead.

Equality and Comparison: A Common Source of Bugs

Comparing Integer values with == is a classic mistake. Because == checks reference equality for objects, two Integer instances with the same value may not be equal unless they are cached.

Integer a = 100; Integer b = 100; System.out.println(a == b); // true, because both use cached value Integer c = 200; Integer d = 200; System.out.println(c == d); // false, because each is a new object

This behavior is confusing and version-dependent. The cache range (-128 to 127) is guaranteed by the JLS, but values outside that range are not cached. Always use .equals() or intValue() when comparing Integer objects.

Integer a = 200; Integer b = 200; if (a.equals(b)) { // correct } if (a.intValue() == b.intValue()) { // also correct }

For int, == works as expected because it compares values directly. This asymmetry is a frequent source of bugs in code that mixes the two types.

Choosing Between int and Integer in Practice

The decision comes down to context. Use int for local variables, arithmetic, loops, and any field where a value is always present. Use Integer when you need nullability, when you are interacting with generics, or when an API requires an object type.

Here is a practical set of criteria:

  • Use int for counters, indices, and numeric calculations where performance and memory matter.
  • Use Integer for entity fields that may be null, such as a database column that allows NULL.
  • Use Integer for all generic collections, because primitives cannot be type arguments.
  • Use Integer when you need to pass a value to a method that expects an Object, such as System.out.println or reflection APIs.

Avoid using Integer as a local variable just to avoid autoboxing; the compiler will often insert conversions anyway. The real cost appears when you create many Integer objects in loops or store them in collections.

Handling Nulls Safely with Integer

When you must use Integer and null is a possibility, handle it explicitly. The OptionalInt class provides a primitive-friendly way to represent optional integers without boxing.

OptionalInt maybeAge = OptionalInt.empty(); if (maybeAge.isPresent()) { int age = maybeAge.getAsInt(); }

But OptionalInt is not a drop-in replacement for Integer in collections. For collections, you still need Integer. In those cases, use null checks or the Objects.equals method for safe comparison.

Integer first = null; Integer second = 5; if (Objects.equals(first, second)) { // handles null safely }

The key is to be explicit about null handling. Do not rely on autoboxing to hide nulls; it will only defer the problem to a NullPointerException later.

Performance Traps in Real Code

One common performance trap is using Integer in a hot loop for arithmetic. Another is repeatedly creating Integer objects through autoboxing in a loop that accumulates values. The JIT can optimize some patterns, but it cannot always eliminate allocation when objects escape.

Integer total = 0; for (int i = 0; i < 1000; i++) { total += i; // unbox, add, rebox each iteration }

This code creates many Integer objects because the cache only covers -128 to 127. The loop total grows beyond that quickly. Using int for total avoids the allocation entirely.

If you need to store a large list of integers and performance is critical, prefer int[] over List<Integer>. The array uses contiguous memory and no object overhead. The list requires an object per element plus the backing array of references.

Compatibility and API Design

When designing APIs, consider what your callers expect. If a method parameter is optional, Integer is appropriate. If it is required, int is better because it forces the caller to provide a value.

public void setAge(int age) { ... } // age must be provided public void setNickname(String nickname) { ... } // can be null

Using Integer for required parameters allows callers to pass null, which you then have to validate. That adds boilerplate and increases the chance of errors. Prefer int unless null is a meaningful state.

In serialization frameworks like Jackson or Gson, Integer fields can be omitted or set to null, while int fields will be defaulted to 0. This can affect JSON output and database mappings. Choose the type based on the semantics of the data, not on convenience.

The choice between int and Integer is not a matter of style. It is a technical decision that affects memory, performance, null safety, and API clarity. By understanding the tradeoffs, you can write code that is both efficient and correct.

java int vs integer: When to Use Each | RYUSLOG DEV