Back to Blog
Java

Java Shift Operators: Signed and Unsigned Bit Shifts

java shift operators: Learn how Java's shift operators work on signed and unsigned integers, including behavior with negative numbers, type promotion, and practical ma...

bitwise operationsinteger arithmeticJava syntaxlow-level programming
Illustration of Java bit shift operators showing bits moving left and right on a number line.

The java shift operators are <<, >>, and >>>. They move the bits of an integer value left or right by a specified number of positions. The result is a new integer value: none of the operators modify the original variable. Although simple in appearance, these operators have behavior that depends on the type of the left operand, the sign of the value, and the shift distance. Misunderstanding any of these leads to subtle bugs that are difficult to spot in code reviews.

The three operators cover two distinct kinds of shifts. << is the signed left shift. It moves bits toward the most significant position and fills the vacated low-order bits with zeros. >> is the signed right shift. It moves bits toward the least significant position and fills the vacated high-order bits with the sign bit, which preserves the sign of negative numbers. >>> is the unsigned right shift. It always fills the high-order vacated bits with zeros, regardless of the sign.

How the Left Shift Operator Works

The left shift operator << moves each bit of the left operand to the left by the number of positions specified by the right operand. The vacated bits on the right are set to zero. For example, the binary value 0000 1010 (10) shifted left by two positions becomes 0010 1000 (40). Each shift position effectively multiplies the value by two, assuming no bits are lost off the top of the type's range.

int a = 10; // binary 0000 1010 int b = a << 2; System.out.println(b); // 40

For positive values, a << n is equivalent to a * 2^n as long as the result fits within the range of the type. The equivalence falls apart when the shift pushes a 1 bit beyond the most significant bit of the type. That discarded bit is lost, and the remaining bits may produce a negative value or a value that no longer corresponds to simple multiplication.

The left shift works the same for int, long, short, and byte after the usual binary numeric promotion. short and byte values are promoted to int before the shift, so the result type is always at least int. A left shift on a byte or short never produces a byte or short result.

Because the shift distance is also subject to masking rules, a << n uses only the lower five bits of n for an int, and the lower six bits of n for a long. That means shifting by 32 on an int is equivalent to shifting by zero. This behavior is surprising when a developer expects a full "clear everything" shift.

Signed Right Shift: Preserving the Sign

The signed right shift operator >> shifts bits to the right and fills the vacated leftmost bits with the sign bit. For a positive number, the sign bit is zero, so the vacated bits become zero. For a negative number, the sign bit is one, so the vacated bits become one. This keeps the sign of the value intact.

int negative = -16; // binary 1111 1111 1111 1111 1111 1111 1111 0000 int shifted = negative >> 2; System.out.println(shifted); // -4

In this example, -16 shifted right by two produces -4, which is the integer division of -16 by 4, rounding toward negative infinity. For positive values, >> n is equivalent to dividing by 2^n and rounding down. For negative odd values, the result is the floor of the division rather than truncation toward zero. The semantic difference matters when a developer expects -3 / 2 to be -1 but Java's signed shift gives -2.

The signed right shift is commonly used when the original sign must remain meaningful, such as when reconstructing a signed value from its high-order bits or implementing certain arithmetic loops. It is also the correct choice when you are extracting signed bit fields from an integer.

Unsigned Right Shift: Filling with Zeros

The unsigned right shift operator >>> shifts bits to the right and always fills the vacated leftmost bits with zero. It does not consider the sign bit. For a positive value, >> and >>> produce the same result. For a negative value, the two operators produce different results because >>> treats the negative number as a large unsigned bit pattern.

int negative = -1; // binary all ones int unsignedShifted = negative >>> 1; System.out.println(unsignedShifted); // 2147483647

The value -1 represented as an int is 0xFFFFFFFF. Shifting it right by one with >>> gives 0x7FFFFFFF, which is 2147483647, the maximum positive int. This behavior is useful when the bit pattern matters more than the signed interpretation, such as when implementing hash functions, compression code, or bit-level protocols.

There is no unsigned left shift operator. A left shift already introduces zeros in the low-order bits, so the signedness only affects how the high-order bits are interpreted. The lack of a dedicated operator means the existing << covers both signed and unsigned left shifts.

Shift Distance Masking and Type Promotion

When the shift distance is not a compile-time constant, Java masks the distance according to the promoted type of the left operand. For an int, the distance is masked with 0x1F (31), so distances between 32 and 63 behave as distances between 0 and 31. For a long, the distance is masked with 0x3F (63), so a distance of 64 becomes zero.

int value = 1; int distance = 32; int result = value << distance; System.out.println(result); // 1, not 0

A shift by 32 on an int is equivalent to a shift by zero because 32 masked by 31 is zero. This is a common source of off-by-one errors when a loop variable that can reach 32 is used directly as a shift distance.

Type promotion also matters. When the left operand is a byte or short, it is promoted to int before the shift. The result is an int, and assigning it back to a byte or short requires an explicit cast. Ignoring the promotion leads to a compile-time error when the result is used where a smaller type is expected.

Practical Uses: Bit Masks and Flags

The most common production use of shift operators is constructing and extracting bit masks. Bit flags are often packed into a single integer for compact storage. Shifting is used to set, clear, or test individual bits.

int FLAG_A = 1 << 0; // 0b0001 int FLAG_B = 1 << 1; // 0b0010 int FLAG_C = 1 << 2; // 0b0100 int flags = 0; flags |= FLAG_A; // set FLAG_A flags |= FLAG_C; // set FLAG_C boolean hasB = (flags & FLAG_B) != 0; System.out.println(hasB); // false

A left shift by a constant creates a power-of-two mask. Combining masks with bitwise OR sets multiple flags in one integer. The bitwise AND test checks whether a particular flag is set. This pattern appears in permission systems, protocol headers, and configuration storage where several boolean values must be represented compactly.

Shifting is also used to extract a range of bits. For example, a color value packed as 32 bits with 8 bits each for alpha, red, green, and blue can be unpacked using a combination of >>> and &.

int argb = 0xFF336699; int alpha = (argb >>> 24) & 0xFF; int red = (argb >>> 16) & 0xFF; int green = (argb >>> 8) & 0xFF; int blue = argb & 0xFF;

The unsigned right shift ensures that the top bits are zeroed before the mask applies. If a signed right shift were used, a negative alpha value would inject ones into the high bits and corrupt the red extraction. This is a concrete example where >>> is the correct choice and >> would be a bug.

Performance and Maintainability Considerations

Shift operators are primitive integer operations that map to single JVM bytecode instructions (ishl, ishr, iushr, and their long counterparts). They do not involve method calls, boxing, or heap allocation. In performance-sensitive code, shifts are often faster than multiplication or division by a power of two because the operation is a direct bit manipulation. That said, the JIT compiler can reliably convert a * 2 into a shift, so substituting shifts for arithmetic rarely changes real-world speed. The practical benefit is more about expressing intent clearly than outperforming the compiler.

From a maintainability perspective, shifts are less readable than arithmetic operations for most developers. Using a * 2 communicates multiplication; a << 1 communicates bit movement. When the intent is bit-level manipulation, the shift is clearer. When the intent is numeric doubling, arithmetic is clearer. The choice should follow the meaning you want to convey, not a guess about performance.

Another aspect of maintainability is avoiding "magic numbers" in shift distances. A shift distance like 5 is meaningless without context. Naming the constant, such as int ORDER = 5; int SIZE = 1 << ORDER;, makes the code easier to read and reduces the chance of an off-by-one error when the value is later adjusted.

Common Mistakes with Shift Operators

One common mistake is using a signed right shift when an unsigned right shift was intended. This happens when a developer extracts a bit field from a value that could be negative in its signed interpretation. The result is an unexpected set of one bits on the left side of the extracted value. The fix is to use >>> when the bit pattern should be treated as unsigned.

Another mistake is assuming that a shift modifies the original variable. The expression value << 2 returns a new value; the variable value remains unchanged. Developers unfamiliar with immutable integer behavior often write value << 2 expecting it to update value. The correct statement is value = value << 2; or the compound assignment value <<= 2;.

A third mistake is failing to account for type promotion. Shifting a byte or short produces an int, so assigning the result back to a byte without a cast fails compilation. Casting the result back is explicit documentation that the higher-order bits are intentionally discarded. Ignoring this creates code that does not compile, or worse, code that compiles after an implicit narrowing that was added automatically by the compiler in an older Java version.

Finally, the shift distance masking rule often surprises developers. Using a variable that exceeds the type's bit width (32 for int, 64 for long) without masking produces a useless result. When the shift distance is not a compile-time constant, it should be normalized explicitly or checked against the type width to avoid the masking behavior.

Shift Operators on Long Values

For long operands, the shift distance is masked with 63 rather than 31. The result type is long. The same sign-filling rules apply: >> preserves the sign bit, and >>> fills with zeros. A common place where long shifts appear is cryptographic code, hash functions, or custom serialization formats where values are 64-bit.

long bits = 0x8000000000000000L; long signed = bits >> 1; // 0xC000000000000000, negative long unsigned = bits >>> 1; // 0x4000000000000000, positive

The distinct behavior between >> and >>> on long is identical in principle to int. The only difference is the shift distance range. The higher range can mask distances of 64 and above, so a shift by 64 becomes a shift by zero. This is a boundary that every developer working with long values should keep in mind.

Choosing the Right Shift Operator

Use << when you need to multiply by a power of two, build bit masks, or pack multiple fields into the high-order bits. Use >> when you want to divide by a power of two while preserving the sign, or when you need to extract a signed bit field. Use >>> when you need to treat the value as a purely unsigned bit pattern, such as in hashing, checksum, or protocol code.

The decision is not about speed but about correctness. The wrong shift operator produces values that are numerically different from what the algorithm expects. In bit-level code, a single incorrect shift can turn a valid hash into a constant, or a correct protocol header into garbage. The operator choice must follow the data's meaning, not a preference for shorter syntax.

A practical rule: if you are extracting a field that should never be negative, use >>>. If you are extracting a field that represents a signed quantity, use >>. For left shifts, the same value is produced regardless of signedness, so choose based on how the result will be interpreted downstream.

The Java shift operators are a small but deep part of the language. They reward attention to signedness, type promotion, and shift distance semantics. Used correctly, they enable compact and efficient bit-level logic. Used carelessly, they produce silent numerical errors that only surface under specific inputs. Keep the difference between >> and >>> in mind, and always verify the type and range of the shift distance before relying on the result.

java shift operators: Practical Usage and Code Examples | RYUSLOG DEV