Back to Blog
Java

Java Bitwise Complement: How ~ Works

java bitwise complement: Learn how Java's bitwise complement operator (~) flips bits, why ~x equals -x-1, and how to use it for bit masking and toggling.

bitwise operatorsJavatwo's complementbit manipulationinteger arithmetic
Diagram showing bitwise complement flipping 0s to 1s and 1s to 0s in a Java integer.

java bitwise complement requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

What the Bitwise Complement Operator Does

In Java, the bitwise complement operator is the unary ~ operator. It flips every bit in its operand: 0 becomes 1, and 1 becomes 0. For example, ~5 produces -6 when applied to an int. This result surprises many developers because the operator works on the binary representation of the number, not on its arithmetic value. The behavior follows directly from how Java stores signed integers using two's complement.

int a = 5; // binary: 0000 0000 0000 0000 0000 0000 0000 0101 int b = ~a; // binary: 1111 1111 1111 1111 1111 1111 1111 1010 System.out.println(b); // prints -6

The complement operator is distinct from the logical NOT operator !, which works only on boolean values. ~ is a bitwise operation that applies to integral types: byte, short, char, int, and long. It cannot be applied to boolean or floating-point types.

Why ~x Equals -x - 1 in Two's Complement

Java's signed integer types use two's complement representation. In this system, the most significant bit is the sign bit. For a positive number, the sign bit is 0; for a negative number, it is 1. The two's complement of a number x is -x, which is obtained by inverting all bits and adding 1. The complement operator ~ performs only the inversion step without the addition.

Given that ~x is the bitwise inversion of x, the relationship ~x = -x - 1 holds for all integer values. For example, ~0 is -1 because inverting all zeros gives all ones, which represents -1 in two's complement. Similarly, ~-1 is 0.

System.out.println(~0); // -1 System.out.println(~-1); // 0 System.out.println(~7); // -8

This identity is useful when you need to derive a value that is the complement of another without performing arithmetic. It also explains why ~ is not a "logical NOT" and why it does not simply negate the number.

Using Bitwise Complement for Bit Masking

One of the most common practical uses of ~ is to create masks that clear specific bits. Suppose you have an integer where certain bits represent flags, and you want to turn off a particular flag without affecting others. You can create a mask with a 1 in the bit position you want to keep and a 0 in the position you want to clear, then apply the AND operator &.

The complement operator makes it easy to build such a mask. For example, to clear bit 3 (the fourth bit from the right) in an integer, you can use ~ (1 << 3) as the mask.

int flags = 0b101011; // some flags int clearBit3 = ~(1 << 3); // mask with bit 3 cleared int result = flags & clearBit3;

Here, 1 << 3 produces 0b1000. Its complement is ...11110111, which has all bits set except bit 3. The AND operation then clears bit 3 in flags while leaving all other bits unchanged.

This pattern is common in low-level programming, such as when working with hardware registers, network protocol flags, or file permission bits. It is also used in algorithms that pack multiple boolean values into a single integer.

Common Mistakes and Type Promotion Pitfalls

A frequent mistake is to use ~ on a byte or short and expect the result to be the same type. Java performs binary numeric promotion on unary operations, so ~ on a byte or short first promotes the operand to int. The result is an int, not the original type. If you assign the result back to a byte or short, you must cast it explicitly.

byte b = 0b00001111; byte result = (byte) ~b; // cast required

Without the cast, the code will not compile because ~b is an int. This is a common source of confusion, especially for developers coming from languages that preserve the operand type.

Another mistake is applying ~ to a char. The char type is unsigned, and its complement produces an int that may be negative if the original character's bit pattern has a high bit set. For example, ~'A' is not a meaningful character; it is an integer with all bits inverted.

Performance and Runtime Behavior

Bitwise operations like ~ are among the fastest operations in the Java virtual machine. They typically compile to a single CPU instruction and do not involve branching or memory allocation. In practice, you do not need to worry about the performance of ~ itself; any performance difference between using ~ and an alternative like x ^ -1 is negligible.

The more important performance consideration is how you use the result. For example, creating a mask inside a loop can be hoisted out of the loop if the mask is constant. The JIT compiler often does this automatically, but writing clear code with constants can help readability and avoid accidental recomputation.

// Constant mask reused outside the loop final int mask = ~(1 << 5); for (int i = 0; i < data.length; i++) { data[i] &= mask; }

In this example, the mask is computed once. If you wrote data[i] &= ~(1 << 5); inside the loop, the JIT would likely still optimize it, but making the constant explicit clarifies intent.

When to Use Complement vs. XOR for Toggling Bits

Both ~ and XOR (^) can be used to manipulate bits, but they serve different purposes. ~ inverts all bits of a value, while XOR toggles specific bits when combined with a mask. To toggle a single bit, you use x ^ (1 << n). To clear a bit, you use x & ~(1 << n). To set a bit, you use x | (1 << n).

The complement operator is essential for clearing bits because it creates a mask with all bits set except the ones you want to clear. Without ~, you would have to write a mask manually, which is error-prone for larger bit positions.

// Clear bit 2 int cleared = x & ~(1 << 2); // Toggle bit 2 int toggled = x ^ (1 << 2); // Set bit 2 int set = x | (1 << 2);

The choice between these operations depends on the desired effect. If you need to invert an entire integer, ~ is the direct approach. If you need to change a specific bit, combine ~ with AND, or use XOR for toggling.

Maintainability and Readability Considerations

Using ~ directly in expression can make code less readable if the intent is not clear. For example, flags & ~(1 << 3) is concise but requires the reader to know that ~(1 << 3) is a mask with bit 3 cleared. Naming the mask can improve maintainability.

private static final int CLEAR_BIT_3 = ~(1 << 3); // Later in code: int updatedFlags = flags & CLEAR_BIT_3;

Defining such constants near the flag definitions makes the code self-documenting. It also centralizes the bit position, so if the bit index changes, you only update one place.

Another readability concern is the use of ~0 to represent all bits set. While ~0 is correct, 0xFFFFFFFF for an int or -1 might be clearer depending on context. In code that deals with bit masks, using ~0 is idiomatic and conveys the intent of "all ones", but it can be confusing to developers unfamiliar with two's complement.

Final Technical Consideration: Unsigned and Long Values

The complement operator works identically on long values, but the bit width is 64 bits. The relationship ~x = -x - 1 still holds. For unsigned semantics, Java does not have unsigned primitive types except char, but you can treat an int as unsigned using Integer.toUnsignedString or by using long for larger ranges. The complement of an unsigned value would be the bitwise inversion, which is the same operation regardless of interpretation.

When working with long, be careful with shifts and masks to avoid sign extension issues. For example, ~0L is -1L, which has all 64 bits set. Using ~0L as a mask is common in bit manipulation libraries.

long allOnes = ~0L; // -1L, all 64 bits set

The bitwise complement is a fundamental tool in Java's bit manipulation arsenal. Understanding its behavior with signed integers and type promotion prevents subtle bugs and allows you to write concise, efficient bit-twiddling code.

java bitwise complement: Practical Usage and Code Examples | RYUSLOG DEV