Java byte, short, int, and long: Differences and Usage
Understand the size, range, conversion, and overflow behavior of Java's byte, short, int, and long types, and when to use each.
Java provides four signed integer types: byte, short, int, and long. They differ in size and value range, which affects memory use, arithmetic behavior, and type conversion. Choosing the right type matters for memory-limited applications, binary data handling, and avoiding overflow bugs.
Size and Range of Each Type
The primary difference is the number of bits each type uses, which determines its minimum and maximum values. All four types use two's complement representation and can store both positive and negative numbers.
| Type | Bits | Minimum | Maximum | Default Value |
|---|---|---|---|---|
| byte | 8 | -128 | 127 | 0 |
| short | 16 | -32,768 | 32,767 | 0 |
| int | 32 | -2,147,483,648 | 2,147,483,647 | 0 |
| long | 64 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 | 0L |
These ranges are fixed by the Java Language Specification and are consistent across platforms. A long literal needs an L suffix when its value exceeds the int range; without the suffix, the literal is treated as an int and the code will not compile.
Choosing the Right Type
The choice depends on the value range and the context. int is the default choice for most arithmetic because the JVM is optimized for 32-bit operations. Use byte or short when memory footprint matters, such as large arrays or binary data. Use long when values may exceed the int range, such as timestamps, file sizes, or scientific calculations.
For example, RGB color components range from 0 to 255, which does not fit directly in a signed Java byte. A common workaround is to store the raw bits in a byte and use b & 0xFF when reading the value, but for ordinary arithmetic use int. The full port range (0-65535) also does not fit in a signed short; use int for that range unless you are handling the unsigned representation explicitly.
However, byte and short are usually promoted to int in arithmetic expressions, so they do not necessarily make calculations faster. They are primarily a memory optimization, not a performance optimization.
Type Conversion and Casting
Java supports implicit widening conversions from a smaller integer type to a larger one. These conversions never lose information. For example, assigning a byte to an int is safe:
byte b = 100; int i = b; // implicit widening, no cast needed
Narrowing conversions require an explicit cast and may lose data. The value is truncated to the lower bits, which can produce unexpected results if the value exceeds the target type's range:
int i = 300; byte b = (byte) i; // 300 mod 256 = 44, so b is 44
This behavior is defined by the Java language. Be careful when casting to avoid silent data loss. Math.toIntExact() (introduced in Java 8) throws ArithmeticException when a long value cannot fit in an int, which is safer for critical conversions.
Arithmetic and Overflow Behavior
When arithmetic is performed on byte, short, or char values, Java promotes them to int before the operation. The result is at least int, and it becomes long if any operand is long. Consider this example:
byte a = 50; byte b = 50; byte sum = (byte) (a + b); // cast required because a + b is int
If you omit the cast, the code will not compile. This promotion is a common source of confusion.
Overflow occurs when a calculation exceeds the maximum or minimum value of the type. Java does not throw an exception on integer overflow; it wraps around using two's complement. For example:
int max = Integer.MAX_VALUE; int overflowed = max + 1; // results in Integer.MIN_VALUE
To prevent silent overflow, use long for intermediate calculations when the result may exceed int range, or use Math.addExact() and similar methods that throw on overflow.
Memory and Performance Considerations
The size differences directly affect memory usage, especially in arrays. A byte[] uses one byte per element, short[] two bytes, int[] four, and long[] eight. For large datasets, using the smallest type that safely holds the data can reduce memory footprint and improve cache locality.
For arithmetic performance, int and long are usually the most efficient because the JVM and CPU are optimized for 32-bit and 64-bit operations. byte and short are promoted to int in expressions, adding conversion overhead. Therefore, prefer them for memory-sensitive data structures rather than for speeding up arithmetic.
Common Pitfalls and Best Practices
Avoid using byte or short as loop counters or for general arithmetic unless you account for implicit promotion. Another common mistake is writing a large long literal without the L suffix:
long big = 1_000_000_000_000L; // without L, this literal does not fit in int and won't compile
When reading binary data, byte is the natural type, but byte is signed. To treat a byte as an unsigned value, use b & 0xFF.
Working with Binary Data
A practical case where integer size matters is parsing a binary file format. For example, reading a 4-byte big-endian integer from a byte array requires assembling the bytes into an int and respecting endianness:
byte[] data = {0x12, 0x34, 0x56, 0x78}; int value = ((data[0] & 0xFF) << 24) | ((data[1] & 0xFF) << 16) | ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
The & 0xFF converts a signed byte to an unsigned value before shifting. Without it, the sign bit would propagate and corrupt the result.
Choosing the right integer type is about matching the range of your data while being aware of memory trade-offs and Java's promotion rules. Prefer int by default, use long for larger values, and reserve byte and short for contexts where memory or data format makes them appropriate.