Back to Blog
Java

Java int vs long: Choosing the Right Primitive

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

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 weigh java int vs 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. The JVM allocates 32 bits for an int and 64 bits for a long. Integer literals in Java default to int; to write a long literal, append an L suffix: 42L.

The 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 and matches the JVM's default arithmetic width. Local variable slots in the JVM are 32 bits wide, so an int occupies one slot while a long occupies two.

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 }

These methods add a small runtime cost because they insert branch checks. They are worth it in validation paths and security-sensitive calculations where silent wrap-around would be dangerous.

Memory and Performance Considerations

The memory difference between int and long matters in two places: heap arrays and the JVM operand stack.

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.

For scalar local variables, the 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.

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; // truncates to 1,705,032,704

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.

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 a long is source-compatible for callers passing literals, but it changes the method signature and breaks binary compatibility. Changing a return type from int to long is also a breaking change for callers that assign the result to an int.

The standard library itself uses both types deliberately. 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.

For values that represent external quantities — file sizes, timestamps, database IDs — prefer long when there is any reasonable chance the value could grow beyond the int range. The memory cost is small for scalar fields, and the cost of a production overflow bug is far higher.

Edge Cases in Collections and Streams

The collections framework is built around int for sizes. ArrayList.size() returns int, and Stream.count() returns long. The Stream.count() method deliberately returns long because streams can produce more than 2^31 elements in theory.

Be careful with IntStream.range versus LongStream.range when generating large ranges:

IntStream.range(0, 1_000_000_000) // works, but limit is Integer.MAX_VALUE LongStream.range(0, 10_000_000_000L) // needed for ranges beyond int

The IntStream API cannot represent ranges that exceed Integer.MAX_VALUE elements. For such ranges, LongStream is the only option. Similarly, IntStream.toArray() returns an int[] whose length is limited by the int 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 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. Keep types consistent within a calculation, and cast explicitly at the boundaries where widening is intentional. A single (long) cast on one operand prevents an entire class of silent data-corruption bugs.

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