Bitwise Operations in Java

A practical walkthrough of bit manipulation in Java — covering the signed vs unsigned right shift, the Integer utility methods I reach for daily, and the gotchas that come with Java's all-signed type system.

Java code editor demonstrating integer bitwise operators
Java Bitwise Operators Unsigned Right Shift Integer Utility Methods BitSet and byte[] FAQ

Java Bitwise Operators Overview

Java supports the same six bitwise operators as C and C++: & (AND), | (OR), ^ (XOR), ~ (NOT), << (left shift), and >> (right shift). It also adds a seventh: the unsigned right shift >>>, which Java developers rely on when working with raw binary data. These operators work on all integer primitives: byte, short, char, int, and long.

The key difference from C is that Java has no unsigned primitive types. Every integer type is signed except char (which is unsigned but designed for Unicode code points). This means that when you work with raw bytes from a network stream or binary file, you need to be careful about sign extension. I have run into this more times than I can count — a byte with value 0xFF read from a socket gets sign-extended to -1 when assigned to an int.

When I ported a C networking library to Java, the main difference I had to account for was that Java's >>> operator exists because Java integers are always signed 32-bit. The logical right shift saved me from manually masking results to simulate unsigned behavior.

// The sign extension trap in Java
byte raw = (byte) 0xFF;       // raw = -1
int val = raw;                 // val = -1 (0xFFFFFFFF), not 255!
int correct = raw & 0xFF;     // correct = 255

// Always mask bytes when converting to int
byte[] header = new byte[4];
// ... read from stream ...
int length = ((header[0] & 0xFF) << 24) |
             ((header[1] & 0xFF) << 16) |
             ((header[2] & 0xFF) <<  8) |
             ((header[3] & 0xFF) <<  0);

The 0xFF Mask Pattern

In my Java projects, I have a helper method static int toUnsigned(byte b) { return b & 0xFF; } that I use everywhere I read raw bytes. Without it, a single 0xFF byte silently becomes 0xFFFFFFFF and corrupts whatever calculation you are doing. Always mask.

Signed Right Shift (>>) vs Unsigned Right Shift (>>>)

This is the Java-specific concept that trips up most developers coming from C or Python. Java has two right shift operators for a reason:

Here is the same negative value shifted both ways so you can see the difference:

int x = -8;  // binary: 11111111 11111111 11111111 11111000

int signedShift   = x >> 2;   // result: -2
// binary: 11111111 11111111 11111111 11111110

int unsignedShift = x >>> 2;  // result: 1073741822
// binary: 00111111 11111111 11111111 11111110

// Real-world use: extracting an unsigned 32-bit value
int packed = 0x87654321;
int top16 = packed >>> 16;  // 0x8765 (unsigned interpretation)

I use >>> most often when working with network protocols and binary file formats where fields span arbitrary bit boundaries. When you combine >>> with a mask, you get a portable way to extract any bit range from an int or long without worrying about the sign bit contaminating your result.

// Extract bits [start, end) from an int (0-indexed, LSB)
public static int extractBits(int value, int start, int end) {
    int width = end - start;
    int mask = (1 << width) - 1;
    return (value >>> start) & mask;
}

// Examples
int val = 0b11011010;
extractBits(val, 0, 4);  // 0b1010 = 10
extractBits(val, 4, 8);  // 0b1101 = 13

Integer and Long Utility Methods for Bit Work

One thing I genuinely appreciate about Java versus C is the built-in bit manipulation API. The Integer and Long wrapper classes include a set of static methods that handle common bit operations in optimized native code. I use these regularly and they are faster than anything I could write by hand:

public class BitUtilsDemo {
    public static void main(String[] args) {
        int x = 0b10110110; // 182

        // Count set bits (popcount)
        System.out.println(Integer.bitCount(x));   // 5

        // Isolate highest/lowest set bit
        System.out.println(Integer.highestOneBit(x)); // 128 (0b10000000)
        System.out.println(Integer.lowestOneBit(x));  // 2   (0b00000010)

        // Count leading/trailing zeros
        System.out.println(Integer.numberOfLeadingZeros(x));  // 25
        System.out.println(Integer.numberOfTrailingZeros(x)); // 1

        // Reverse and rotate
        System.out.println(Integer.reverse(x));
        System.out.println(Integer.rotateLeft(x, 4));
        System.out.println(Integer.rotateRight(x, 4));

        // Unsigned string representation
        System.out.println(Integer.toBinaryString(x));
        System.out.println(Integer.toUnsignedString(0xFFFFFFFFL)); // 4294967295
    }
}

A few of these methods are worth special attention. bitCount() uses a hardware popcount instruction on modern JVMs and runs in a single cycle. numberOfTrailingZeros() is the fastest way to find the least significant set bit — I use it in hash table implementations to compute bucket indices from hash codes. highestOneBit() is useful for rounding down to the nearest power of two, which is common in memory allocator design.

Java 8+ Unsigned API

Starting with Java 8, Integer and Long gained methods for treating values as unsigned: toUnsignedString(), divideUnsigned(), remainderUnsigned(), and compareUnsigned(). These avoid the need to move to long or BigInteger just to handle unsigned 32-bit arithmetic. For example, Integer.divideUnsigned(0x80000000, 2) returns 1073741824, not -1073741824.

BitSet and byte[] — Working with Bit Collections

When you need to manage a collection of bits beyond a single int or long, Java provides java.util.BitSet. This class implements a growable bit vector that can hold millions of bits efficiently. Internally it uses a long[] array, so operations like AND, OR, and XOR operate on 64 bits at a time.

// BitSet example — tracking seen integers
import java.util.BitSet;

BitSet seen = new BitSet(1_000_000);

// Mark an integer as seen
seen.set(452319);

// Check if seen
if (seen.get(452319)) {
    System.out.println("Already processed");
}

// Bulk operations
BitSet subset = seen.get(400000, 500000);
subset.andNot(seen);  // set difference

// Streaming and cardinality
long cardinality = seen.cardinality();  // number of set bits
seen.stream().forEach(i -> System.out.println(i));

For raw byte[] manipulation, the same AND, OR, XOR logic applies, but you have to handle each byte individually. I have a small utility class I carry between projects:

// XOR a byte array against a key (simple stream cipher)
public static byte[] xorBytes(byte[] data, byte[] key) {
    byte[] result = new byte[data.length];
    for (int i = 0; i < data.length; i++) {
        result[i] = (byte) (data[i] ^ key[i % key.length]);
    }
    return result;
}

// Count set bits in a byte array
public static int popcount(byte[] data) {
    int count = 0;
    for (byte b : data) {
        count += Integer.bitCount(b & 0xFF);
    }
    return count;
}

Try It Yourself

Use our interactive bitwise calculator to see exactly how the signed and unsigned right shift differ. Enter -8, then shift right by 2 using both operators and watch the binary representation update live.

Frequently Asked Questions About Bitwise Operations in Java

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

The >> operator performs a signed (arithmetic) right shift that preserves the sign bit by shifting in 1s for negative numbers. The >>> operator performs an unsigned (logical) right shift that always shifts in 0s from the left, regardless of the sign. For example, -8 >> 2 = -2, but -8 >>> 2 = 1073741822 on a 32-bit int.

Does Java support unsigned integer types?

Java does not have unsigned primitive types for byte, short, int, or long. However, Java 8 introduced utility methods in Integer and Long classes that treat values as unsigned: Integer.toUnsignedString(), Integer.divideUnsigned(), Integer.remainderUnsigned(), and Long equivalents. The >>> operator also treats its left operand as unsigned by zero-filling during right shift.

What bit manipulation utility methods does Java provide?

The Integer and Long classes provide several useful bit methods: Integer.bitCount() counts set bits, Integer.highestOneBit() returns a value with only the highest set bit, Integer.lowestOneBit() returns the lowest set bit, Integer.numberOfLeadingZeros() and Integer.numberOfTrailingZeros() count leading and trailing zeros, Integer.reverse() reverses bit order, and Integer.rotateLeft()/rotateRight() perform circular bit shifts.

How do I work with individual bits in a Java byte array?

Java byte values are signed (-128 to 127). When extracting bits from a byte, you must mask with 0xFF to avoid sign extension: int b = rawByte & 0xFF;. For setting bits in a byte array, use OR to set and AND with mask to clear. The java.util.BitSet class provides a higher-level API for managing bit arrays with get(), set(), clear(), and and(), or(), xor() operations.

Why does Java not have unsigned types like C?

James Gosling and the Java designers made a deliberate choice to omit unsigned integer types, believing they added complexity and were a frequent source of bugs (as seen in C). The language design philosophy was that programmers rarely need the full unsigned range for arithmetic, and when they do, the unsigned utility methods in Java 8+ cover most cases. For bit manipulation, the >>> operator and 0xFF mask pattern provide equivalent functionality.

Related Tools

Bitwise Calculator →

AND, OR, XOR, NOT, shifts

Bitwise Guide →

Complete operations reference

Hex to ASCII →

Decode hex strings

SHA256 Generator →

Compute hashes