Type Casting β Find the Bug¶
Practice finding and fixing bugs in Java code related to Type Casting. Each exercise contains buggy code β your job is to find the bug, explain why it happens, and fix it.
How to Use¶
- Read the buggy code carefully
- Try to find the bug without looking at the hint
- Write the fix yourself before checking the solution
- Understand why the bug happens β not just how to fix it
Difficulty Levels¶
| Level | Description |
|---|---|
| π’ | Easy β Common beginner mistakes, basic casting errors |
| π‘ | Medium β Autoboxing traps, type promotion surprises, precision loss |
| π΄ | Hard β Type erasure, bridge methods, JVM-level casting behavior |
Bug 1: Lost Decimal π’¶
What the code should do: Calculate the average of two integers and print the result with decimals.
public class Main {
public static void main(String[] args) {
int a = 7;
int b = 2;
double average = a / b;
System.out.println("Average: " + average);
}
}
Expected output:
Actual output:
π‘ Hint
Look at the division `a / b` β what types are the operands?π Bug Explanation
**Bug:** Integer division is performed before the widening cast to `double`. **Why it happens:** Both `a` and `b` are `int`, so `a / b` performs integer division (result is `int` 3). Only then is the result widened to `double` 3.0. **Impact:** The decimal part is lost β all integer divisions produce truncated results.β Fixed Code
**What changed:** Cast `a` to `double` before division, forcing floating-point division.Bug 2: The Overflowing Byte π’¶
What the code should do: Add two bytes and store the result in a byte.
public class Main {
public static void main(String[] args) {
byte x = 100;
byte y = 100;
byte sum = x + y;
System.out.println("Sum: " + sum);
}
}
Expected output:
Actual output:
π‘ Hint
What type does `byte + byte` produce in Java?π Bug Explanation
**Bug:** `byte + byte` is promoted to `int` by Java's type promotion rules. **Why it happens:** The JVM operand stack uses minimum `int`-sized slots. All `byte`, `short`, and `char` operands are promoted to `int` before arithmetic. **Impact:** Compilation error because `int` cannot be assigned to `byte` without explicit cast.β Fixed Code
public class Main {
public static void main(String[] args) {
byte x = 100;
byte y = 100;
byte sum = (byte)(x + y); // Explicit cast β but beware: 200 overflows byte!
System.out.println("Sum: " + sum); // Prints -56 (overflow!)
// Better approach: use int to avoid overflow
int safeSum = x + y;
System.out.println("Safe sum: " + safeSum); // Prints 200
}
}
Bug 3: ClassCastException Surprise π’¶
What the code should do: Process an animal based on its type.
public class Main {
static class Animal { String name; Animal(String n) { name = n; } }
static class Dog extends Animal { Dog(String n) { super(n); } void bark() { System.out.println(name + " barks!"); } }
static class Cat extends Animal { Cat(String n) { super(n); } void meow() { System.out.println(name + " meows!"); } }
public static void main(String[] args) {
Animal animal = new Cat("Whiskers");
Dog dog = (Dog) animal;
dog.bark();
}
}
Expected output:
Actual output:
π‘ Hint
What is the actual runtime type of `animal`?π Bug Explanation
**Bug:** Downcasting a `Cat` object to `Dog` β they are siblings, not in the same hierarchy path. **Why it happens:** The compiler allows the cast because `Animal` could theoretically be a `Dog`. But at runtime, the object is a `Cat`, which cannot be cast to `Dog`. **Impact:** `ClassCastException` at runtime.β Fixed Code
public class Main {
static class Animal { String name; Animal(String n) { name = n; } }
static class Dog extends Animal { Dog(String n) { super(n); } void bark() { System.out.println(name + " barks!"); } }
static class Cat extends Animal { Cat(String n) { super(n); } void meow() { System.out.println(name + " meows!"); } }
public static void main(String[] args) {
Animal animal = new Cat("Whiskers");
// Safe approach: check type before casting
if (animal instanceof Dog dog) {
dog.bark();
} else if (animal instanceof Cat cat) {
cat.meow();
}
}
}
Bug 4: Precision Trap π‘¶
What the code should do: Check if a large long value is preserved after conversion to double and back.
public class Main {
public static void main(String[] args) {
long original = 9007199254740993L; // 2^53 + 1
double d = original; // Widening β should be safe?
long restored = (long) d;
System.out.println("Original: " + original);
System.out.println("Restored: " + restored);
System.out.println("Equal: " + (original == restored));
}
}
Expected output:
Actual output:
π‘ Hint
How many bits of mantissa does `double` have? Is it enough for all `long` values?π Bug Explanation
**Bug:** `long β double` widening loses precision for values larger than 2^53. **Why it happens:** `double` has 52 bits of mantissa (53 including implicit leading 1). Values beyond 2^53 cannot be represented exactly β they are rounded to the nearest representable value. **Impact:** Silent data corruption β the value changes without any exception or warning.β Fixed Code
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
long original = 9007199254740993L;
// Use BigDecimal for lossless conversion
BigDecimal bd = BigDecimal.valueOf(original);
long restored = bd.longValueExact();
System.out.println("Original: " + original);
System.out.println("Restored: " + restored);
System.out.println("Equal: " + (original == restored));
}
}
Bug 5: The Null Unboxing π‘¶
What the code should do: Calculate total price from a nullable discount.
public class Main {
public static void main(String[] args) {
double basePrice = 100.0;
Integer discountPercent = getDiscount(); // May return null
double finalPrice = basePrice * (1 - discountPercent / 100.0);
System.out.println("Final price: " + finalPrice);
}
static Integer getDiscount() {
return null; // No discount available
}
}
Expected output:
Actual output:
π‘ Hint
What happens when Java tries to unbox a `null` `Integer` to use in arithmetic?π Bug Explanation
**Bug:** Unboxing `null` `Integer` to `int` causes `NullPointerException`. **Why it happens:** When `discountPercent` (which is `null`) is used in arithmetic, Java tries to auto-unbox it to `int`. Unboxing `null` throws NPE. **Impact:** Runtime crash in any arithmetic involving nullable wrapper types.β Fixed Code
public class Main {
public static void main(String[] args) {
double basePrice = 100.0;
Integer discountPercent = getDiscount();
// Safe: check for null before unboxing
int discount = (discountPercent != null) ? discountPercent : 0;
double finalPrice = basePrice * (1 - discount / 100.0);
System.out.println("Final price: " + finalPrice);
}
static Integer getDiscount() {
return null;
}
}
Bug 6: Integer Cache Gotcha π‘¶
What the code should do: Compare two Integer values for equality.
public class Main {
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
System.out.println("127 == 127: " + (a == b));
Integer c = 128;
Integer d = 128;
System.out.println("128 == 128: " + (c == d));
}
}
Expected output:
Actual output:
π‘ Hint
Java caches `Integer` objects for values -128 to 127. What happens outside this range?π Bug Explanation
**Bug:** `==` compares object references, not values, for `Integer` objects. **Why it happens:** For values -128 to 127, `Integer.valueOf()` returns cached objects, so `==` works. For values outside this range, new objects are created, so `==` compares different references. **Impact:** Inconsistent equality behavior depending on the value β extremely confusing bug.β Fixed Code
public class Main {
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
System.out.println("127 equals 127: " + a.equals(b)); // true
Integer c = 128;
Integer d = 128;
System.out.println("128 equals 128: " + c.equals(d)); // true
// Or use intValue() for primitive comparison
System.out.println("128 == 128: " + (c.intValue() == d.intValue())); // true
}
}
Bug 7: Wrong Remove π‘¶
What the code should do: Remove the value 3 from a list of integers.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
System.out.println("Before: " + numbers);
numbers.remove(3);
System.out.println("After removing 3: " + numbers);
}
}
Expected output:
Actual output:
π‘ Hint
`List` has two `remove` methods: `remove(int index)` and `remove(Object o)`. Which one is called with `3`?π Bug Explanation
**Bug:** `numbers.remove(3)` calls `remove(int index)` β removes element at index 3 (which is `4`), not the value `3`. **Why it happens:** Java prefers `remove(int)` over autoboxing to `remove(Integer)` because widening/exact match takes priority over boxing in method resolution. **Impact:** Wrong element removed β data corruption.β Fixed Code
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
System.out.println("Before: " + numbers);
numbers.remove(Integer.valueOf(3)); // Explicitly box to call remove(Object)
System.out.println("After removing 3: " + numbers);
}
}
Bug 8: Generic Type Erasure Cast π΄¶
What the code should do: Store and retrieve typed values from a generic container.
import java.util.ArrayList;
import java.util.List;
public class Main {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
List rawList = new ArrayList();
rawList.add(42);
rawList.add("hello");
List<String> strings = rawList; // Unchecked assignment
for (String s : strings) { // Where does the ClassCastException happen?
System.out.println(s.toUpperCase());
}
}
}
Expected output:
Actual output:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
π‘ Hint
Where does the hidden `checkcast` instruction appear in the bytecode? It is not at the assignment line.π Bug Explanation
**Bug:** Raw type `List` bypasses generic type checking. The `ClassCastException` occurs at the for-each loop, where the compiler inserts a hidden `checkcast String` during the `Iterator.next()` call. **Why it happens:** Type erasure removes generic info at runtime. The raw list accepts `Integer` and `String`. When iterating as `Listβ Fixed Code
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
// Option 1: Use proper generics from the start
List<Object> items = new ArrayList<>();
items.add(42);
items.add("hello");
for (Object item : items) {
if (item instanceof String s) {
System.out.println(s.toUpperCase());
} else {
System.out.println(item);
}
}
// Option 2: Use Collections.checkedList() to catch early
List<String> safeStrings = java.util.Collections.checkedList(
new ArrayList<>(), String.class);
// safeStrings.add(42); // Throws ClassCastException immediately!
}
}
Bug 9: Double Cast Surprise π΄¶
What the code should do: Unbox an Object containing an Integer to a long.
public class Main {
public static void main(String[] args) {
Object obj = 42; // Autoboxed to Integer
long value = (long) obj;
System.out.println("Value: " + value);
}
}
Expected output:
Actual output:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long
π‘ Hint
`(long) obj` tries to cast `Object` to `Long` (unboxing), not widening from `Integer`.π Bug Explanation
**Bug:** `(long) obj` attempts to cast `Object` to `Long`, then unbox to `long`. But the actual object is `Integer`, not `Long`. **Why it happens:** You cannot unbox an `Integer` to `long` β you must first unbox to `int`, then widen to `long`. The JVM performs at most one implicit conversion at a time. **Impact:** `ClassCastException` that is confusing because the syntax looks like a primitive cast but is actually a reference cast + unboxing.β Fixed Code
public class Main {
public static void main(String[] args) {
Object obj = 42;
// Option 1: Cast to Integer first, then widen
long value1 = (Integer) obj;
System.out.println("Value: " + value1);
// Option 2: Use Number for flexibility
long value2 = ((Number) obj).longValue();
System.out.println("Value: " + value2);
}
}
Bug 10: NaN Comparison Trap π΄¶
What the code should do: Detect if a cast result is valid by comparing with the original.
public class Main {
public static void main(String[] args) {
double value = Double.NaN;
int cast = (int) value;
// Validate the cast
if (cast == (int) value) {
System.out.println("Cast is valid: " + cast);
} else {
System.out.println("Cast is invalid");
}
// Detect NaN
if (value == Double.NaN) {
System.out.println("Value is NaN");
} else {
System.out.println("Value is not NaN"); // Surprise!
}
}
}
Expected output:
Actual output:
π‘ Hint
NaN is never equal to anything β including itself. And `(int) NaN` produces 0 per the JLS.π Bug Explanation
**Bug:** Two issues: 1. `(int) NaN` produces `0` (JLS 5.1.3), so `cast == (int) value` is `0 == 0` β true. 2. `value == Double.NaN` is always `false` because NaN is not equal to anything, including itself. This is per IEEE 754 specification. **Why it happens:** NaN has special comparison semantics in IEEE 754. All comparisons with NaN return false, including `NaN == NaN`. **Impact:** NaN values slip through validation, and casts of NaN produce 0 without warning.β Fixed Code
public class Main {
public static void main(String[] args) {
double value = Double.NaN;
// Detect NaN FIRST using the correct method
if (Double.isNaN(value)) {
System.out.println("Value is NaN β cannot cast safely");
} else {
int cast = (int) value;
System.out.println("Cast is valid: " + cast);
}
}
}
Bug 11: Ternary Type Widening π΄¶
What the code should do: Return either an int or a double based on a condition.
public class Main {
public static void main(String[] args) {
boolean useWhole = true;
Object result = useWhole ? 42 : 3.14;
System.out.println("Result: " + result);
System.out.println("Type: " + result.getClass().getSimpleName());
}
}
Expected output:
Actual output:
π‘ Hint
In a ternary expression with numeric operands of different types, binary numeric promotion applies to BOTH sides.π Bug Explanation
**Bug:** The ternary operator applies binary numeric promotion β since one operand is `int` (42) and the other is `double` (3.14), the `int` is widened to `double`. Both branches produce `double`. **Why it happens:** JLS 15.25.2 β conditional expression type is determined by the types of BOTH branches, not just the selected one. **Impact:** The result is always `Double`, even when the `int` branch is selected.β Fixed Code
public class Main {
public static void main(String[] args) {
boolean useWhole = true;
// Use separate variables to avoid type promotion
if (useWhole) {
int result = 42;
System.out.println("Result: " + result);
System.out.println("Type: int");
} else {
double result = 3.14;
System.out.println("Result: " + result);
System.out.println("Type: double");
}
// Or use Number as the common type
Number result = useWhole ? Integer.valueOf(42) : Double.valueOf(3.14);
System.out.println("Result: " + result);
System.out.println("Type: " + result.getClass().getSimpleName());
}
}
Score Card¶
| Bug | Difficulty | Topic | Found it? | Understood why? |
|---|---|---|---|---|
| 1 | π’ | Integer division before widening | β | β |
| 2 | π’ | Type promotion in byte arithmetic | β | β |
| 3 | π’ | Unsafe downcast without instanceof | β | β |
| 4 | π‘ | Precision loss in longβdouble | β | β |
| 5 | π‘ | Null unboxing NPE | β | β |
| 6 | π‘ | Integer cache boundary | β | β |
| 7 | π‘ | List.remove(int) vs remove(Object) | β | β |
| 8 | π΄ | Generic type erasure hidden cast | β | β |
| 9 | π΄ | Objectβlong cast mismatch | β | β |
| 10 | π΄ | NaN comparison and cast behavior | β | β |
| 11 | π΄ | Ternary operator type promotion | β | β |
In this topic