Back to Blog
Java

Java int vs long: Choosing the Right Primitive

Compare Java's int and long primitives: ranges, memory cost, overflow behavior, conversion pitfalls, and practical selection criteria for production code.

Java primitivesinteger overflowtype conversionmemory usageJVM performance
Diagram comparing Java int and long primitive types showing their bit widths and value ranges with a memory footprint illustration

The decision between int and long in Java comes down to one question: can your values fit within a 32-bit signed range? An int holds values from -2,147,483,648 to 2,147,483,647. A long holds values from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807. When you compare int and long, the width difference drives every other consideration: memory footprint, overflow behavior, conversion rules, and API design.

The Core Difference Between int and long

Both int and long are signed two's-complement primitives. An int uses 32 bits; a long uses 64 bits. Integer literals in Java default to int; to write a long literal, append an L suffix: 42L.

The Java Language Specification (JLS) defines the exact ranges. Integer.MAX_VALUE is 2,147,483,647, and Long.MAX_VALUE is 9,223,372,036,854,775,807. These constants are useful when you need to check whether a value fits in the narrower type before casting.

When int Is the Right Choice

Most everyday values fit comfortably in an int: loop counters, array indices, small counters, and configuration values. Using int keeps memory usage at 4 bytes per value.

For array storage, the difference is direct. An int[] of size N uses 4N bytes for elements; a long[] uses 8N bytes. When storing millions of values, this doubles the memory footprint and reduces cache locality during iteration.

int[] measurements = new int[1_000_000]; long[] measurementsLong = new long[1_000_000];

The first array consumes roughly 4 MB for its elements; the second consumes roughly 8 MB. If your data fits in the int range, the smaller array is the better choice for memory-bound workloads.

When long Is Necessary

Certain categories of values routinely exceed the int range:

  • Timestamps in milliseconds or nanoseconds since epoch
  • File sizes in bytes for large files
  • Database auto-increment IDs that may grow past 2.1 billion
  • Monetary calculations in minor units for high-volume systems
  • Hash seeds or random state that need wider internal representation
long fileSizeBytes = Files.size(Paths.get("/data/large-file.bin")); long timestampMillis = System.currentTimeMillis();

These values originate from external systems or time itself, so you do not control their magnitude. Choosing long here is defensive by design.

Overflow: The Silent Failure Mode

The most dangerous consequence of choosing int when long is needed is silent overflow. Java integer arithmetic wraps around using two's-complement semantics. No exception is thrown when an int overflows.

int max = Integer.MAX_VALUE; int overflowed = max + 1; // -2,147,483,648, no error

This is a classic production bug. A counter that increments past Integer.MAX_VALUE wraps to a negative value, breaking logic that assumes non-negative values. The same wrap-around occurs with long, but the range is so much larger that it is rarely reached in practice.

The Math.addExact, Math.subtractExact, and Math.multiplyExact methods throw ArithmeticException on overflow. Use them when you want to detect overflow explicitly rather than let it wrap silently.

try { int result = Math.addExact(Integer.MAX_VALUE, 1); } catch (ArithmeticException e) { // handle overflow explicitly }

Memory and Performance Considerations

The memory difference between int and long shows up most clearly in heap arrays and in JVM local variable slots: an int occupies one 32-bit slot, while a long occupies two. For scalar local variables, however, the runtime performance difference is usually negligible on 64-bit JVMs. The JIT compiler can keep both int and long values in registers. Addition, subtraction, and multiplication have similar cost for both types on modern hardware.

For heap arrays, long[] uses twice the memory of int[] for the same element count. This affects cache behavior: an int[] fits more elements per cache line, reducing memory bandwidth pressure during sequential iteration.

Division and modulo are the exceptions. Integer division is more expensive than addition or multiplication, and long division can be slightly slower than int division on some architectures. In tight numerical loops, this difference can become measurable, but it rarely dominates overall runtime.

Do not micro-optimize by choosing int purely for speed. The overflow risk far outweighs the marginal performance gain in most applications. Measure first if you suspect division is a bottleneck.

Conversions Between int and long

Java performs widening conversions automatically. Assigning an int to a long is always safe because every int value fits in a long.

int small = 100; long widened = small; // implicit, no cast needed

The reverse requires an explicit cast and can lose data.

long large = 5_000_000_000L; int narrowed = (int) large; // 705,032,704; upper bits are discarded

Narrowing a long to an int keeps only the lower 32 bits. This is a common source of subtle bugs when code receives a long from an API but stores it in an int field. If you want a checked narrowing conversion, Math.toIntExact(long) throws ArithmeticException when the value does not fit in an int.

When mixing int and long in arithmetic, the int operand is promoted to long before the operation:

int a = 1_000_000; long b = 2_000_000_000L; long result = a * b; // both promoted to long, result is 2e15

But if both operands are int, the multiplication happens in 32-bit arithmetic before widening:

int a = 1_000_000; int b = 2_000_000; long result = a * b; // multiplication overflows int first, then widens

This is a classic bug. The product a * b is 2,000,000,000,000, which exceeds the int range. The multiplication wraps before the assignment to long. The fix is to cast at least one operand to long before multiplying:

long result = (long) a * b; // correct

API Design and Type Selection

When designing a public API, the choice between int and long affects compatibility. Changing an int parameter to long changes the method descriptor and breaks binary compatibility, even though many source call sites still compile via widening. Changing a return type from int to long is a breaking change for callers that assign the result to an int.

For values that represent external quantities, prefer long when there is any reasonable chance the value could grow beyond the int range.

Collections and Streams

The standard library uses both types in different places. List.size() returns int because collection sizes are limited to Integer.MAX_VALUE in practice. Stream.count() returns long because streams can theoretically produce more than 2^31 elements.

When generating large ranges, the stream API reflects the same distinction:

IntStream.range(0, 1_000_000_000) // end is an int LongStream.range(0, 10_000_000_000L) // needed for ranges beyond int

An IntStream.range call uses int bounds, so a range starting at 0 cannot go beyond Integer.MAX_VALUE. For larger ranges, use LongStream.range.

Choosing Between int and long in Practice

Use int for array indices, loop counters, and values that are guaranteed to stay within ±2.1 billion. Use long for timestamps, file sizes, database IDs, and any value that originates from an external system where you do not control the magnitude.

When in doubt, prefer long for scalar fields in domain objects. The memory difference is normally 4 bytes per field, which is negligible for most applications. The risk of silent overflow in int is a production incident waiting to happen.

For large arrays, the memory difference is significant. If you are storing millions of numeric values and you know the range fits in int, use int[] to halve the memory footprint and improve cache locality. If values can exceed the int range, long[] is the only correct choice.

The final consideration is consistency. Mixing int and long in the same calculation requires careful casting to avoid the overflow-before-widening bug. In a multiplication of two int values whose product exceeds the int range, casting one operand to long prevents that bug.

Java int vs long: Practical Usage and Code Examples | RYUSLOG DEV