JAVA / OPERATORS
Bitwise and shift operators on integers
Use &, |, ^, ~, <<, >> and >>> on Java ints to read and change individual bits, and predict what happens with negative values and shift counts over 31.
What you will learn
- Pack independent flags into one int with 1 << n, then set, clear and test them
- Predict >> versus >>> on negative ints from which bit refills the vacated slots
- Explain why 1 << 32 is 1: int shift counts use only the low 5 bits of the distance
- Cast back to byte, short or char after bitwise ops, since operands widen to int
Understanding Bitwise and shift operators on integers
An int in Java is exactly 32 bits in two's complement, with the leftmost bit carrying the sign. The operators &, |, ^ and ~ work column by column on those bits: each output bit depends only on the bits in the same position of the inputs, so there is no carry and nothing can overflow. That is why 12 & 10 is 8 and not anything resembling addition, since only the 8s column holds a 1 in both operands. These operators consume ints, not booleans, and Java has no truthiness, so a nonzero result is not automatically a condition and if (flags & MASK) does not compile.
Left shift moves bits toward the high end and fills the vacated low positions with zeros, which multiplies by two per step until significant bits reach the sign bit and then fall off the left edge entirely. Right shift comes in two forms because the vacated high positions must be filled with something: >> copies the current sign bit downward so negatives stay negative, while >>> always inserts zeros and therefore reads the pattern as unsigned. This makes >> floor division by a power of two rather than the truncating division / performs, so -7 >> 1 is -4 while -7 / 2 is -3.
Operands narrower than int are promoted before any bit is touched, so byte, short and char widen to 32 bits and the result is an int you must cast back down yourself. The same 32-bit view explains why ~12 is -13 instead of 3: every bit flips, and flipping all bits in two's complement is exactly -x - 1. Shift distances are constrained too, since only the low 5 bits of the right operand count for an int shift, so 1 << 32 means 1 << 0 and yields 1; a long shift uses the low 6 bits instead.
public class Bits {
static String bin(int x) {
return String.format("%8s", Integer.toBinaryString(x & 0xFF)).replace(' ', '0');
}
public static void main(String[] args) {
int a = 0b1100; // 12
int b = 0b1010; // 10
System.out.println("a = " + bin(a) + " (" + a + ")");
System.out.println("b = " + bin(b) + " (" + b + ")");
System.out.println("a & b = " + bin(a & b) + " (" + (a & b) + ")");
System.out.println("a | b = " + bin(a | b) + " (" + (a | b) + ")");
System.out.println("a ^ b = " + bin(a ^ b) + " (" + (a ^ b) + ")");
System.out.println("a << 2 = " + bin(a << 2) + " (" + (a << 2) + ")");
System.out.println("a >> 1 = " + bin(a >> 1) + " (" + (a >> 1) + ")");
System.out.println("~a = " + bin(~a) + " (" + ~a + ") low 8 bits only");
}
}Bitwise and shift operators act on the 32 fixed two's-complement bits of an int, so the sign bit and integer promotion explain every surprising result.
Worked examples
Permission flags in a single int
Stores three independent yes/no settings in one int and updates them without disturbing each other.
public class Flags {
static final int READ = 1 << 0; // 0001
static final int WRITE = 1 << 1; // 0010
static final int EXECUTE = 1 << 2; // 0100
public static void main(String[] args) {
int perms = READ | WRITE;
System.out.println("perms = " + perms);
System.out.println("can write = " + ((perms & WRITE) != 0));
System.out.println("can execute = " + ((perms & EXECUTE) != 0));
perms |= EXECUTE;
perms &= ~WRITE;
System.out.println("perms = " + perms);
System.out.println("can write = " + ((perms & WRITE) != 0));
}
}Example explained
Line 11 << 0, 1 << 1 and 1 << 2 give 1, 2 and 4, so each constant owns a distinct bit and two flags can never collide.
Line 2perms & WRITE keeps only that one column, producing 2 or 0, which is why the test compares against 0 instead of being used directly as a condition.
Line 3perms |= EXECUTE turns one bit on and leaves the rest alone, because OR with a 0 bit returns the original bit.
Line 4perms &= ~WRITE clears exactly one bit: ~WRITE is 31 ones with a single zero at position 1, so 7 becomes 5.
Shift distances are taken modulo 32
Shows that an int shift uses only the low five bits of the distance, while a long shift uses six.
public class ShiftCounts {
public static void main(String[] args) {
System.out.println(1 << 31);
System.out.println(1 << 32);
System.out.println(1 << 33);
System.out.println(1L << 32);
System.out.println(-1 >>> 28);
System.out.println(-1 >> 28);
}
}Example explained
Line 11 << 31 parks the single bit on the sign bit, so the pattern reads as the most negative int rather than about two billion.
Line 21 << 32 uses 32 & 31, which is 0, so nothing moves at all; 1 << 33 shifts by one and gives 2.
Line 31L << 32 succeeds because the left operand is a long, whose shift distance is masked with 63 instead of 31.
Line 4-1 is 32 one bits: >>> 28 leaves only four of them (15), while >> 28 refills from the sign bit and stays -1.
Promotion happens before the shift
Demonstrates that byte and char operands become 32-bit ints first, which changes the result of >>> and of |.
public class Promotion {
public static void main(String[] args) {
byte b = -1;
System.out.println(b >>> 4);
System.out.println((b & 0xFF) >>> 4);
char c = 'A';
System.out.println(c | 0x20);
System.out.println((char) (c | 0x20));
}
}Example explained
Line 1byte b = -1 is eight one bits, but b >>> 4 widens it to a 32-bit -1 first, so 28 ones survive the shift.
Line 2b & 0xFF discards the 24 bits that promotion introduced, so the unsigned shift now behaves as intended on 8-bit data.
Line 3c | 0x20 yields the int 97 because char is promoted, which is why println prints a number instead of a letter.
Line 4The (char) cast reinterprets that same 97 as a character, giving 'a'.
Important notes
There is no <<< operator, because a left shift always brings in zeros; the signed/unsigned choice only exists for right shifts.
byte b = -1; b >>>= 4; leaves b at -1, since the shift runs on the promoted int and the implicit narrowing cast throws away the zeros that were shifted in.
Common mistakes
Writing if (perms & WRITE == 0): == binds tighter than &, so it parses as perms & (WRITE == 0) and fails to compile with a bad operand types error on int and boolean.
Extracting the top byte of a packed colour with rgb >> 24: the sign bit is copied down, so an alpha of 0xFF arrives as -1 instead of 255 unless you use >>> 24 or add & 0xFF.
Substituting x >> 1 for x / 2 in code that can see negative input: the shift rounds toward negative infinity, so -7 becomes -4 while the division gives -3, and totals silently drift.
Try it yourself
Change, predict, then run
Pack the date 2026-09-03 into one int as (year << 9) | (month << 5) | day, then recover the three fields using >>> together with the masks 0xF and 0x1F and print them.
Open the Java workspaceCheck your understanding
For which int values of x does x >> 1 give a different answer from x / 2?
- Negative odd values of x
- All negative values of x
- Values above 2^30, where the shift overflows
- Never; >> 1 and / 2 are defined to produce the same result
Show answer
Right shift discards the low bit and rounds toward negative infinity, while / truncates toward zero, so they only disagree when a 1 bit is dropped from a negative value: -7 >> 1 is -4 but -7 / 2 is -3. "All negative values" is tempting but wrong, because even negatives lose nothing when shifted: -8 >> 1 and -8 / 2 are both -4.