Java's byte is a signed 8-bit primitive type with values from -128 through 127. That range is easy to overlook because bytes are often expected to represent 0 through 255. section 4.13
The main hazard appears when byte values participate in larger integer expressions. A negative byte widens to int with ones in its higher bits, so bitwise composition can produce the wrong result. section 4.13
int readTwoBytes() {
byte lo = readLowByte();
byte hi = readHighByte();
return (hi << 8) | lo;
}
This two-byte composition looks plausible but lets a negative low byte sign-extend before the OR operation. section 4.13
Mask bytes before combining them
If readLowByte() returns -1, widening changes it to 0xFFFFFFFF, causing the unmasked expression to return -1 regardless of the high byte. Mask each byte first: ((hi & 0xFF) << 8) | (lo & 0xFF). section 4.13
Compound assignments add another trap: b += 256 widens, adds, and then narrows back to byte, restoring the original value instead of converting -1 to 255. Similar narrowing can make an unsigned shift appear ineffective. section 4.9
Avoid byte-related mistakes by:
- Using int or long where compact storage is not required. section 4.9
- Masking byte values before bitwise operations or shifts. section 4.13
- Encapsulating low-level byte manipulation in well-tested utility methods. section 4.13
- Treating static-analysis warnings as useful but incomplete, since tools cover only particular patterns. section 4.13 section 4.9

