Topic 26 of 119 3. Operators in Java Interactive Tutorial

Bitwise Computing in Java: AND, OR, XOR, Shift, and Complement

14 mins read Last updated: 22 Aug 2026 19,498 views

Direct Answer & Core Concept

Java bitwise operators manipulate individual binary bits of the integral types, which are `byte`, `short`, `char`, `int` and `long`. Bitwise AND `&` gives 1 only where both operands have 1, bitwise OR `|` gives 1 where either operand has 1, and bitwise XOR `^` gives 1 only where the two bits differ. The complement operator `~` flips every bit, so `~n` equals `-(n + 1)`. The three shift operators work differently: `<<` moves bits left, which multiplies by a power of two, `>>` moves bits right while copying the sign bit so a negative number stays negative, and `>>>` moves bits right while filling the vacated high bits with zeros, discarding the sign. Java has no unsigned integer types, so `>>>` plus helpers such as `Integer.toUnsignedString` and `Integer.compareUnsigned` are how unsigned arithmetic is done. Two details catch people out: the shift count is masked to the width of the type, so `1 << 32` is 1 rather than 0 because the count wraps to 0 for an `int`, and the same applies to a negative count such as `12 << -1`, which shifts by 31. The compound forms `&=`, `|=`, `^=`, `<<=`, `>>=` and `>>>=` also insert an implicit narrowing cast, which is why `byte b; b &= 10;` compiles when `b = b & 10;` does not.

Detailed Explanation & Mechanics

1. The Complete List of Bitwise Operators

Bitwise operators work on the individual bits of an integral value. In Java they accept byte, short, char, int and long, and they are rejected on boolean, float and double.

OperatorNameOperandsResult bit is 1 when
&Bitwise ANDTwoBoth bits are 1
|Bitwise ORTwoAt least one bit is 1
^Bitwise XORTwoThe two bits differ
~Bitwise complementOneUnary, flips every bit
<<Left shiftTwoUnary family, moves bits toward the high end
>>Arithmetic right shiftTwoCopies the sign bit into the vacated bits
>>>Logical right shiftTwoFills the vacated bits with zeros

The single most common mistake is writing & or | when you meant && or ||. They look similar and behave very differently, as section 10 shows. There is also no ~ for logical negation; that is !.

2. Truth Tables and a Worked Example

Each operator follows its own truth table, applied to every bit position independently. Nothing carries between positions, there is no arithmetic and no overflow behaviour to think about.

Main.java
Run Online
AND & OR | XOR ^ a b -> r a b -> r a b -> r -------- -------- -------- 0 0 -> 0 0 0 -> 0 0 0 -> 0 0 1 -> 0 0 1 -> 1 0 1 -> 1 1 0 -> 0 1 0 -> 1 1 0 -> 1 1 1 -> 1 1 1 -> 1 1 1 -> 0

Here is the classic worked example, one bit position at a time.

Main.java
Run Online
a = 0b1100 = 12 b = 0b1010 = 10 a b 1100 1010 ---- ---- AND 1000 = 8 only the bit set in both survives ---- ---- OR 1110 = 14 every bit set in either survives ---- ---- XOR 0110 = 6 the bits that disagree
Main.java
Run Online
int a = 0b1100;
// 12
int b = 0b1010;
// 10
System.out.println(a & b);
// 8
System.out.println(a | b);
// 14
System.out.println(a ^ b);
// 6

Java also accepts the 0b prefix for binary literals, added in Java 7, which makes this far easier to read than decimal. Underscores may be used for grouping.

Main.java
Run Online
int mask = 0b1010_1010;
long big = 0xFFFF_FFFF_FFFF_FFFFL;

3. The Complement Operator ~

~ flips every bit of the value. As covered in the unary operators topic, in two's complement that is exactly the same as negating and subtracting one, so ~n equals -(n + 1).

ExpressionResultReason
~12-13Equals -(12 + 1)
~0-1All bits become 1, which is -1
~~nnFlipping twice restores the original
~1.0compile errorbad operand type double for unary operator '~'
Main.java
Run Online
int a = 0b1100;
// 12
System.out.println(~a);
// -13
System.out.println(~a == -(a + 1));
// true
System.out.println(~~a == a);
// true

The clearing idiom mask & ~FLAG depends on this, because ~FLAG turns a single-bit mask into an all-ones-except-that-bit value.

4. The Three Shift Operators

The shifts are where Java causes the most confusion, because one of the three behaves differently from the other two.

OperatorNameVacated high bitsEffect on positive numbers
<<Left shiftFilled with zerosMultiplies by 2 to the power n
>>Arithmetic right shiftFilled with the sign bitDivides by 2 to the power n
>>>Logical right shiftFilled with zerosDivides by 2 to the power n

Left shift is always the same, because shifting a positive value up cannot change the sign.

Main.java
Run Online
System.out.println(12 > 2);
//  3, which is 12 / 4
System.out.println(1

The right shifts only differ once the value is negative, and then the difference is stark.

Main.java
Run Online
-8 as a 32 bit two's complement number

          11111111 11111111 11111111 11111000

        -8 >> 1      arithmetic, copies the leading 1 into the hole

          11111111 11111111 11111111 11111100   =  -4

        -8 >>> 1     logical, fills the hole with zeros

          01111111 11111111 11111111 11111100   =  2147483644

        the value is the same either way, 2147483644 is the correct reading of -8 / 2
        when you treat the bits as unsigned:  4294967288 / 2 = 2147483644
Main.java
Run Online
int n = -8;
System.out.println(n >> 1);
// -4
System.out.println(n >>> 1);
// 2147483644, a large positive number
System.out.println(n >> 31);
// -1, every bit is a 1
System.out.println(n >>> 28);
// 15, the low four bits, all ones

A handy trick with >>> is isolating the low bits of a negative number, since >> would drag sign ones down through the result while >>> leaves zeros in the high end.

5. The Shift Count Is Masked to the Type Width

This is the detail that surprises experienced programmers coming from C. Java does not require the shift count to be less than the width, and it does not treat an out-of-range count as an error. It masks the count down to the width of the type being shifted.

Main.java
Run Online
for an int the count is masked with 0x1f(31) for a long the count is masked with 0x3f(63) so the effective shift is always: count & 0x1f for int count & 0x3f for long 1 1

Negative counts are not rejected either. They are masked the same way, and -1 has every bit set, so masking it gives the largest legal shift.

That last pair is why a shift by a variable that happens to be negative produces a baffling result. Always mask a shift count you do not control yourself, and remember that for an int the mask is 0x1f, not 0xff.

6. No Unsigned Types: Working Around It

Java has only signed integers, so int runs from -2147483648 to 2147483647. Many low-level formats, such as IPv4 addresses and file sizes, are naturally unsigned, which leaves three options: shift with >>> and convert explicitly, use the unsigned helper methods on the wrapper classes, or store the value in a long.

Main.java
Run Online
int n = -1;
System.out.println(n > 0);
// false, signed comparison
System.out.println(Integer.compareUnsigned(n, 0) > 0);
// true, unsigned comparison
System.out.println(Integer.toUnsignedString(n));
// 4294967295
System.out.println(Integer.divideUnsigned(n, 2));
// 2147483647

The full set of unsigned helpers on Integer and Long covers comparison, division, remainder, string conversion and parsing.

MethodPurpose
Integer.toUnsignedString(n)Print a value as if it were unsigned
Integer.compareUnsigned(a, b)Compare two values as unsigned
Integer.divideUnsigned(a, b)Divide as unsigned
Integer.remainderUnsigned(a, b)Remainder as unsigned
Long.compareUnsigned(a, b)Same helpers for long
Integer.parseUnsignedInt(s)Parse a number above Integer.MAX_VALUE

A worked example, unpacking an IPv4 address stored in one int.

Main.java
Run Online
int ip =(192 24) & 0xFF;
int octet2 =(ip >>> 16) & 0xFF;
int octet3 =(ip >>> 8) & 0xFF;
int octet4 = ip & 0xFF;
System.out.println(octet1 + "." + octet2 + "." + octet3 + "." + octet4);
Main.java
Run Online
192.168.1.100 ip >>> 24 moves the top octet down to the bottom & 0xFF masks it to exactly one byte, discarding the rest this is the standard unpack pattern: shift, then mask

7. Practical Pattern: Even and Odd Without Division

Testing the lowest bit tells you whether a number is even, and it avoids a division entirely.

Main.java
Run Online
System.out.println(42 & 1);
// 0, so 42 is even
System.out.println(43 & 1);
// 1, so 43 is odd

The same trick gives you a fast test for divisibility by any power of two, because n % 8 and n & 7 always agree.

Main.java
Run Online
System.out.println((45 & 7) ==(45 % 8));
// true
System.out.println(42 & 7);
// 2
System.out.println(45 & 7);
// 5

Because 7 is 111 in binary, ANDing with it keeps only the low three bits, which is exactly what the remainder operation computes.

8. Practical Pattern: Flag Bits With Set, Test, Clear, Toggle

This is the most common real use of these operators. Each flag gets one bit, so a whole set of booleans fits inside a single int.

GoalExpressionWhy
Turn a flag onflags |= FLAGOR forces the bit to 1
Turn a flag offflags &= ~FLAGAND with the inverse clears it
Flip a flagflags ^= FLAGXOR flips exactly that bit
Test a flag(flags & FLAG) != 0Non-zero means the bit was set
Main.java
Run Online
int flags = 0;
int FLAG_READ = 1

Declaring each flag as 1 << n rather than writing the decimal number makes the intent obvious and stops anyone from wondering which bit is which.

Main.java
Run Online
static final int FLAG_READ = 1

9. Swapping Two Values With XOR

The interview answer is to use the fact that XOR is its own inverse: a ^ a is 0 and a ^ 0 is a, so XORing the same value twice cancels out.

Main.java
Run Online
a ^ a == 0 XOR a value with itself gives zero a ^ 0 == a XOR with zero gives it back unchanged three steps swap two variables with no temporary start: a = 5, b = 9 step 1 a = a ^ b a = 5^9 = 12, b = 9 step 2 b = a ^ b b = 12^9 = 5, a = 12 step 3 a = a ^ b a = 12^5 = 9, b = 5 end: a = 9, b = 5 swapped
Main.java
Run Online
int a = 5, b = 9;
a = a ^ b;
b = a ^ b;
a = a ^ b;
System.out.println(a + ", " + b);
// 9, 5

The compound form does the same thing more concisely.

Main.java
Run Online
int a = 5, b = 9;
a ^= b;
b ^= a;
a ^= b;
System.out.println(a + ", " + b);
// 9, 5

Two points worth keeping straight. First, the swap is genuinely correct even when the two values are equal, which is the fault many people claim: swapping 7 and 7 leaves both at 7, and swapping 0 and 0 leaves both at 0. Second, do not do this in real code. It is slower than a plain temporary variable on modern processors, which can execute the three loads and stores in parallel, and it is far harder to read. Know it for interviews; use a temporary for production.

Main.java
Run Online
int a = 5, b = 9;
int temp = a;
a = b;
b = temp;
System.out.println(a + ", " + b);
// 9, 5, and readable

10. Bitwise and Logical: & vs &&

Java has both a bitwise and a logical form of AND and OR, and they behave very differently on boolean values. & and | always evaluate both sides, while && and || short-circuit and skip the right-hand side when the answer is already known.

Main.java
Run Online
static boolean t() {
    System.out.println("  t() called");
    return true;
}static boolean f() {
    System.out.println("  f() called");
    return false;
}boolean r = f() & t();
// calls both, t() is evaluated needlessly
boolean r2 = f() && t();
// f() is false, so t() is never called
Main.java
Run Online
f() & t() -> f() called t() called result false, both sides ran f() && t() -> f() called result false, t() was skipped entirely

For int operands the distinction is not optional, because only & and | work on them.

ExpressionOperandsShort-circuits
a & bIntegral or booleanNo, always evaluates both
a | bIntegral or booleanNo, always evaluates both
a && bboolean onlyYes
a || bboolean onlyYes
a && b on intsCompile errorOnly booleans are accepted

For ordinary conditions always use && and ||. The bitwise versions exist for the flag arithmetic shown earlier, not for combining tests.

11. Precedence: Parenthesize Bit Expressions

The equality operators bind more tightly than the bitwise ones, which is a genuine trap.

Main.java
Run Online
System.out.println(5 ^ 5 == 0);
// does not compile
System.out.println((5 ^ 5) == 0);
// true

== is evaluated first, so the compiler tries to apply ^ to an int and a boolean and reports *bad operand types for binary operator '^'*. The same applies to & and | in conditions.

Main.java
Run Online
5 ^ 5 == 0 what you wrote: 5 ^(5 == 0) what the parser sees:(5 ^ 5) == 0 so 5 == 0 is evaluated first, producing a boolean, and then the compiler tries to XOR an int with a boolean -> error

In a flag test always write the parentheses explicitly, since it is the correct expression anyway and tells the reader exactly what is being checked.

Main.java
Run Online
if ((flags & FLAG_READ) != 0) {
    // readable and unambiguous
}

12. The Compound Bitwise Operators

&=, |=, ^=, <<=, >>= and >>>= are the shorthands, and like all compound assignments they insert an implicit narrowing cast.

Main.java
Run Online
int n = 12;
n &= 10;
System.out.println(n);
// 8
n = 12;
n |= 1;
System.out.println(n);
// 13
n = 12;
n ^= 5;
System.out.println(n);
// 9
n = 12;
n = 2;
System.out.println(n);
// 3
n = -8;
n >>>= 2;
System.out.println(n);
// 1073741822

This matters on the narrow types, where the written-out form does not compile at all.

Main.java
Run Online
byte b = 12;
b &= 10;
// legal, the cast is inserted
System.out.println(b);
// 8
byte c = 12;
c = c & 10;
// error: incompatible types: possible lossy conversion from int to byte

The reason is the promotion rule from the arithmetic topic: b & 10 promotes to int, but b &= 10 narrows back automatically.

13. Bitwise Cheat Sheet

QuestionAnswer
Bitwise operators& | ^ ~ << >> >>>
Valid operand typesbyte, short, char, int, long
12 & 108
12 | 1014
12 ^ 106
~n equals-(n + 1)
12 << 248, multiplies by 4
12 >> 23, divides by 4
-8 >> 1-4, sign preserved
-8 >>> 12147483644, zeros filled
1 << 31Integer.MIN_VALUE
1 << 321, the count is masked to 5 bits
1L << 641, a long masks to 6 bits
1 << -1MIN_VALUE, since -1 & 31 = 31
Unsigned integer types in JavaNone exist, use >>> and the helper methods
Unsigned helpersInteger.toUnsignedString, compareUnsigned, divideUnsigned
Is a number even(n & 1) == 0
Set a flagflags |= FLAG
Clear a flagflags &= ~FLAG
Toggle a flagflags ^= FLAG
Test a flag(flags & FLAG) != 0
Swap without a temporaryThree XOR steps, correct but slow, avoid in real code
5 ^ 5 == 0Compile error, == binds tighter
&& versus &&& short-circuits, & does not
Compound form on a byteLegal, b &= 10 casts implicitly

Bitwise operators are worth learning properly, but their uses are narrower than they look. The three shifts are the most useful, since << replaces multiplication by a power of two and >>> is how you read the high bits of a packed value. The flag set, test, clear and toggle pattern is the second most useful. Everything else, including the famous XOR swap, is mostly interview material.

Runnable Code Example: Bitwise Computing in Java

Java 17+
Main.java
Run Online
public class BitwiseDemo {
    public static void main(String[] args) {
        // ---------- the three two-operand bitwise operators ----------
        int a = 0b1100;
        // 12
        int b = 0b1010;
        // 10
        System.out.println("a       = " + a + "   " + Integer.toBinaryString(a));
        System.out.println("b       = " + b + "   " + Integer.toBinaryString(b));
        System.out.println("a & b   = " +(a & b) + "   " + Integer.toBinaryString(a & b));
        System.out.println("a | b   = " +(a | b) + "   " + Integer.toBinaryString(a | b));
        System.out.println("a ^ b   = " +(a ^ b) + "   " + Integer.toBinaryString(a ^ b));
        // ---------- complement ----------
        System.out.println("~a      = " +(~a) + "   which is -(a + 1)");
        // ---------- the three shifts ----------
        System.out.println("a << 2  = " +(a << 2) + "   same as a * 4");
        System.out.println("a >> 2  = " +(a >> 2) + "   same as a / 4");
        // ---------- signed versus unsigned right shift ----------
        int negative = -8;
        System.out.println("negative       = " + negative);
        System.out.println("negative >> 1  = " +(negative >> 1) + "   sign preserved");
        System.out.println("negative >>> 1 = " +(negative >>> 1) + "   zeros filled in");
        // ---------- testing and setting a single bit ----------
        int flags = 0;
        int FLAG_READ = 1 << 2;
        flags |= FLAG_READ;
        // set it
        boolean hasRead =(flags & FLAG_READ) != 0;
        // test it
        System.out.println("flags after set = " + flags + ", hasRead = " + hasRead);
    }
}

Common Java Technical Interview Questions & Answers

Interview Prep
Q1

What is the difference between >> and >>> in Java?

>> is the arithmetic right shift: it shifts the bits right and copies the sign bit into the positions vacated at the top, so a negative number stays negative and -8 >> 1 is -4. >>> is the logical right shift: it fills those positions with zeros, discarding the sign, so -8 >>> 1 is 2147483644. They give identical results for any non-negative value, which is why the difference often goes unnoticed until a negative number appears.
Q2

What does 1 << 32 evaluate to in Java?

It is 1, not 0. Java masks the shift count to the width of the type being shifted, so for an int the count is masked with 0x1f and 32 & 31 is 0, making the shift a shift by zero. The same masking applies to 1 << 33, which is 2 because 33 & 31 is 1, and to negative counts, where 1 << -1 shifts by 31 and gives Integer.MIN_VALUE. A long masks with 0x3f instead, so 1L << 32 is 4294967296.
Q3

How do you swap two numbers without a temporary variable?

Use three XOR steps, because XOR is its own inverse: a ^ a is 0 and a ^ 0 is a. So a = a ^ b; b = a ^ b; a = a ^ b; swaps the two. Note that it is correct even when the values are equal, which is a commonly repeated but incorrect objection. In real code use a temporary variable, since this is slower on modern processors and much harder to read.
Q4

What does the bitwise complement operator ~ do?

It flips every bit of the operand. Since Java uses two's complement for all signed integers, ~n is exactly equal to -(n + 1), so ~12 is -13 and ~0 is -1. It also means ~ is its own inverse, because ~~n always equals n. The operator is rejected on float and double with bad operand type.
Q5

How do you check whether a number is even or odd using bitwise operators?

AND the number with 1 and look at the lowest bit. If (n & 1) == 0 the number is even, and if (n & 1) == 1 it is odd. This avoids a division entirely. The same idea extends to divisibility by any power of two, because (n & 7) always equals n % 8 for the low three bits.
Q6

How do you set, clear, test and toggle a single bit?

Set it with flags |= FLAG, clear it with flags &= ~FLAG, toggle it with flags ^= FLAG, and test it with (flags & FLAG) != 0. The ~ in the clear step matters, since it is what inverts the mask and forces the target bit to zero. Declaring each flag as 1 << n rather than as a decimal number makes the intent clear.
Q7

What is the difference between & and &&?

& is bitwise and | is bitwise OR, while && and || are the logical forms. The bitwise versions always evaluate both operands, whereas the logical forms short-circuit and skip the right-hand side once the answer is known. Only the bitwise versions accept int operands, and only the logical forms accept nothing but booleans, so using && on two ints is a compile error. For ordinary conditions always use && and ||.
Q8

Why does 5 ^ 5 == 0 fail to compile?

Because == binds more tightly than ^. The compiler evaluates 5 == 0 first, producing a boolean, and then tries to XOR that boolean with an int, reporting bad operand types for binary operator '^'. The correct form is (5 ^ 5) == 0, where the parentheses force the XOR to happen first. This is a good example of why bit expressions in conditions should always carry explicit parentheses.
Q9

Does Java have unsigned integer types?

No. All the integer types are signed, so an int runs from -2147483648 to 2147483647. For naturally unsigned values, shift with >>> to discard the sign and use the helpers on the wrapper classes: Integer.toUnsignedString, Integer.compareUnsigned, Integer.divideUnsigned, Integer.remainderUnsigned, and parseUnsignedInt for values above MAX_VALUE. The alternative is to store the value in a long, which has enough headroom.
Q10

Why does byte b; b &= 10; compile when byte c = 12; c = c & 10; does not?

Because plain b & 10 is binary arithmetic, so it promotes the byte to int and produces an int, which cannot be assigned back to a byte without a cast. The compound form b &= 10 is a compound assignment, so the compiler inserts the narrowing cast automatically, exactly as it does for b += 10.
Q11

What is 0xFF used for when unpacking bytes from an int?

It is a mask of eight ones, and ANDing with it keeps exactly the low eight bits and discards everything above them. The standard pattern is to shift the byte down to the position you want and then mask it, as in (ip >>> 24) & 0xFF for the top octet of an IPv4 address. The shift alone would leave the other bits in place, so the two steps are always needed together.
Q12

When should you actually use bitwise operators?

For flag sets packed into a single integer, for multiplying and dividing by powers of two with the shift operators, and for reading the fields out of a packed format such as a network header. Outside those cases they are rarely clearer than the alternatives, and the classic XOR swap should stay an interview answer rather than production code.

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Use bitwise operators where they genuinely earn their place: packing a set of flags into one integer, multiplying and dividing by powers of two with the shift operators, and reading fields out of a packed binary format such as a network header. Everywhere else, prefer multiplication, division, or a plain boolean field, because the bitwise version is far harder for the next person to read and a JIT will usually compile the arithmetic form to the same instructions. Two habits prevent most real bugs. First, always mask a shift count you did not write yourself, using & 0x1f for an int and & 0x3f for a long, because Java silently masks the count to the type width and a negative or oversized count produces a baffling result instead of an error. Second, parenthesise every bit expression used in a condition, since == and != bind more tightly than & | and ^ and the compiler will not warn you. Keep the distinctions explicit: use && and || for conditions rather than & and |, and reach for >>> with the unsigned helpers such as Integer.toUnsignedString and Integer.compareUnsigned whenever your data is naturally unsigned, since Java has no unsigned types and a plain signed comparison will quietly give you the wrong answer. Declare flags as 1 << n with a name rather than as bare numbers, and remember that the XOR swap trick, while correct even for equal values, is slower than a temporary variable and should stay an interview answer rather than production code.
We value your privacy & transparency

OperateTools uses cookies and local storage to ensure basic tool functionality, preserve preferences, and analyze aggregated traffic. We never sell your personal data or index your document files. Learn more in our Privacy Policy.