Imagine that you are building an employee payroll application.
You receive an employee's age as an int:
int age = 35;Later, some calculation requires a long.
long employeeAge = age;Java accepts this without any special syntax.
But now suppose you have a salary stored as a double:
double salary = 45678.75;and you try:
int salaryWithoutDecimal = salary;Java refuses to compile it.
Why?
Both examples involve numbers. Both involve moving a value from one variable to another. So why does Java allow the first conversion automatically but reject the second?
That question takes us directly into type conversion and type casting.
1. Before Type Conversion — Remember What a Type Means#
A variable does not merely hold a value.
Its data type tells Java:
- what kind of value the variable can store,
- approximately what range of values it supports,
- how Java should interpret the stored bits,
- which operations are valid on that value.
For the primitive numeric types, keep this mental model:
| Type | Size | General Role |
|---|---|---|
byte | 8 bits | Very small integers |
short | 16 bits | Small integers |
int | 32 bits | Normal integer calculations |
long | 64 bits | Large integers |
float | 32 bits | Floating-point values |
double | 64 bits | Higher-precision floating-point values |
char | 16 bits | UTF-16 code unit / unsigned numeric value |
boolean | JVM-dependent representation | true or false |
Now think about this:
int number = 100;
long result = number;A value that fits inside an int will also fit inside the magnitude range of a long.
But consider:
long number = 9_000_000_000L;
int result = number;An int cannot represent every possible long value.
Java therefore cannot blindly perform that conversion without your explicit instruction.
This brings us to the first important concept.
2. Type Conversion#
Type conversion means changing or interpreting a value from one data type as another data type.
For example:
int number = 100;
double result = number;Here:
int
↓
doubleThe value originally belongs to the int type, but Java converts it to double.
Output:
100.0Type conversions broadly appear in two forms:
Type Conversion
│
├── Automatic / Implicit Conversion
│ └── Usually widening
│
└── Explicit Conversion
└── Usually narrowing / castingDo not memorize those words yet.
We will build them naturally.
3. Why Does Java Sometimes Convert Automatically?#
Consider:
byte smallNumber = 50;
int normalNumber = smallNumber;byte has a much smaller integer range than int.
So every valid byte value can be represented as an int.
Conceptually:
Small container
↓
Larger compatible containerJava can perform this safely with respect to numeric range.
That is why Java does not require you to write a cast.
4. Implicit Conversion#
When Java performs a conversion automatically without an explicit cast written by the programmer, we commonly call it implicit conversion.
Example:
public class Main {
public static void main(String[] args) {
int number = 100;
long result = number;
System.out.println(result);
}
}Output:
100There is no cast:
(long) numberJava handled the conversion.
5. Widening Primitive Casting#
Now we can give the more precise name.
When a primitive numeric value is converted to a type with a wider permitted numeric range according to Java's widening-conversion rules, it is called a widening primitive conversion.
Java defines these widening primitive conversions:
byte
├── short
├── int
├── long
├── float
└── double
short
├── int
├── long
├── float
└── double
char
├── int
├── long
├── float
└── double
int
├── long
├── float
└── double
long
├── float
└── double
float
└── doubleThese are the widening primitive conversions defined by the Java Language Specification.
A common simplified memory chain is:
byte → short → int → long → float → doubleBut be careful: char is separate.
char → int → long → float → doublechar does not widen to short.
5.1 Small Example#
public class Main {
public static void main(String[] args) {
byte value = 10;
int result = value;
System.out.println(result);
}
}Output:
10Execution:
byte value = 10
↓
Java sees int destination
↓
byte → int is valid widening
↓
conversion happens automatically
↓
result = 105.2 Multiple Widening Conversions#
public class Main {
public static void main(String[] args) {
byte byteValue = 100;
short shortValue = byteValue;
int intValue = byteValue;
long longValue = byteValue;
float floatValue = byteValue;
double doubleValue = byteValue;
System.out.println(shortValue);
System.out.println(intValue);
System.out.println(longValue);
System.out.println(floatValue);
System.out.println(doubleValue);
}
}Output:
100
100
100
100.0
100.06. Important Trap — Widening Does Not Always Mean Exact#
This is an extremely important interview point.
You may hear:
"Widening conversion never loses data."
That statement is too broad.
Consider:
int number = 1_234_567_890;
float result = number;
System.out.println(number);
System.out.println(result);A float supports a much larger magnitude range than int.
So:
int → floatis a widening conversion.
But float does not have enough precision to exactly represent every 32-bit integer.
Therefore some least-significant information can be lost.
The Java Language Specification explicitly notes that int → float, long → float, and long → double can lose precision even though they are widening conversions. Widening itself does not throw a runtime exception.
Remember#
Wider range ≠ always greater exact precisionThat distinction becomes extremely important in financial and scientific programs.
7. A Useful Comparison — Range vs Precision#
Imagine these two questions:
Question 1#
How large a number can the type represent?
That concerns range.
Question 2#
How accurately can the exact digits be represented?
That concerns precision.
float has a huge range compared with int, but it cannot exactly represent every int.
Example:
public class Main {
public static void main(String[] args) {
int original = 16_777_217;
float converted = original;
System.out.println(original);
System.out.println(converted);
}
}Typical result:
16777217
1.6777216E7The exact integer 16,777,217 cannot be represented exactly in a 32-bit IEEE 754 float.
8. Why Does Java Reject the Opposite Direction?#
Now consider:
double price = 199.99;
int amount = price;What problems could occur?
First:
199.99contains a fractional portion.
int cannot represent .99.
Second, double can represent magnitudes far beyond the int range.
So converting:
double
↓
intcan lose information.
Java therefore requires you to explicitly say:
"Yes, I understand that information may be lost."
That is what a cast expresses.
9. Explicit Conversion#
When the programmer explicitly requests a conversion, we call it explicit conversion.
Syntax:
(targetType) valueExample:
double price = 199.99;
int amount = (int) price;Here:
(int)is the cast operator.
Complete program:
public class Main {
public static void main(String[] args) {
double price = 199.99;
int amount = (int) price;
System.out.println(amount);
}
}Output:
199Notice what happened.
Java did not round 199.99 to 200.
The fractional portion was discarded when narrowing the floating-point value to an integral type.
10. Narrowing Casting#
A conversion from a broader numeric type to a narrower primitive type is generally called a narrowing primitive conversion.
Examples:
double → float
double → long
double → int
double → short
double → byte
double → char
float → long
float → int
float → short
float → byte
float → char
long → int
long → short
long → byte
long → char
int → short
int → byte
int → char
short → byte
short → char
char → byte
char → shortThe Java Language Specification defines narrowing conversions as conversions that can lose magnitude, range, or precision.
11. Basic Narrowing Example#
public class Main {
public static void main(String[] args) {
double number = 45.98;
int result = (int) number;
System.out.println(result);
}
}Output:
45Execution:
45.98
↓
cast to int
↓
fractional portion removed
↓
4512. Casting Is Not Rounding#
This is worth reinforcing.
double value = 9.99;
int result = (int) value;Result:
9Not:
10Likewise:
double value = -9.99;
int result = (int) value;Result:
-9Java rounds toward zero when converting floating-point values to integral values.
Conceptually:
9.99 → 9
-9.99 → -913. If You Actually Want Rounding#
Then casting alone is not enough.
For example:
public class Main {
public static void main(String[] args) {
double value = 9.99;
long rounded = Math.round(value);
System.out.println(rounded);
}
}Output:
10So these solve different problems:
(int) valuemeans:
Convert to int, discarding the fractional portion according to Java's narrowing rules.Whereas:
Math.round(value)means:
Perform rounding.
14. Numeric Type Conversion#
Now that widening and narrowing are clear, let us connect the numeric types.
Widening#
byte value = 10;
short a = value;
int b = value;
long c = value;
float d = value;
double e = value;Usually no cast is required.
Narrowing#
double value = 10.5;
float a = (float) value;
long b = (long) value;
int c = (int) value;
short d = (short) value;
byte e = (byte) value;A cast is normally required.
15. Data Loss During Casting#
This is where many beginners become confused.
Consider:
int number = 130;
byte result = (byte) number;
System.out.println(result);You might predict:
130But a byte can only represent:
-128 to 127So 130 does not fit.
The output is:
-126Why?
This is not Java randomly choosing a number.
A narrowing conversion to byte retains only the low 8 bits of the integer representation. Java's specification describes narrowing integral conversion as discarding all except the target type's low-order bits.
Simplified:
130
binary form:
00000000 00000000 00000000 10000010
Keep only lowest 8 bits:
10000010As a signed Java byte, that bit pattern represents:
-12616. Another Data-Loss Example#
public class Main {
public static void main(String[] args) {
int number = 255;
byte result = (byte) number;
System.out.println(result);
}
}Output:
-1Oracle's specification gives the same kind of example: narrowing 255 to byte produces -1.
17. Does Narrowing Throw an Exception When the Number Does Not Fit?#
Normally, no.
This surprises many beginners.
int number = 1000;
byte result = (byte) number;Java does not automatically throw:
ArithmeticExceptionor:
NumberFormatExceptionIt performs the defined narrowing conversion.
Oracle explicitly specifies that narrowing primitive conversion can lose information, overflow, or underflow without itself causing a runtime exception.
That means you must ensure the conversion is valid for your business requirement.
18. Safe Range Check Before Narrowing#
Suppose your application receives an int, but a legacy protocol requires a byte.
Do not blindly write:
byte result = (byte) value;Instead:
public class Main {
public static void main(String[] args) {
int value = 120;
if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
byte result = (byte) value;
System.out.println(result);
} else {
System.out.println("Value cannot safely fit in byte");
}
}
}This approach prevents silent corruption.
19. A Better Tool — Math.toIntExact()#
Suppose you need:
long → intbut you do not want silent overflow.
Instead of:
long value = 5_000_000_000L;
int result = (int) value;you can use:
long value = 5_000_000_000L;
int result = Math.toIntExact(value);If the value does not fit in int, Java throws an ArithmeticException.
This is often much safer in production code when overflow represents invalid data.
20. char Is Special#
A new concept has appeared here.
char looks like a character type:
char letter = 'A';But internally, a Java char represents an unsigned 16-bit UTF-16 code unit.
That is why numeric conversions involving char are possible.
21. char to Numeric Conversion#
Consider:
public class Main {
public static void main(String[] args) {
char letter = 'A';
int value = letter;
System.out.println(value);
}
}Output:
65The numeric value associated with 'A' here is 65.
Likewise:
char letter = 'a';
int value = letter;produces:
9722. Numeric Value to char#
Now reverse the idea.
public class Main {
public static void main(String[] args) {
int value = 65;
char character = (char) value;
System.out.println(character);
}
}Output:
ANotice that the cast is needed:
(char) valuebecause:
int → charis a narrowing conversion.
23. Common Mistake — Digit Character vs Numeric Digit#
This is extremely common.
Suppose:
char digit = '5';What does this produce?
int number = digit;It does not produce numeric 5.
It produces the UTF-16 value of '5', which is:
53Example:
public class Main {
public static void main(String[] args) {
char digit = '5';
int number = digit;
System.out.println(number);
}
}Output:
5324. Converting '5' to Numeric 5#
For decimal digits:
char digit = '5';
int number = digit - '0';
System.out.println(number);Output:
5Why?
'5' = 53
'0' = 48
53 - 48 = 5A more semantic approach is:
int number = Character.getNumericValue(digit);For validating decimal digits, you might also use:
Character.isDigit(digit)depending on the requirement.
25. Compile-Time Constant Conversion — A Very Important Interview Trap#
Look at this:
byte number = 100;100 is normally an int literal.
So why does this compile?
Because Java permits certain constant expressions of type int to be assigned to byte, short, or char without an explicit cast when the constant value is representable in the destination type.
This compiles:
byte a = 100;
short b = 1000;
char c = 65;But this does not:
byte a = 200;because 200 does not fit in a byte.
And this is different:
int value = 100;
byte result = value;Even though the runtime value happens to be 100, value is a non-constant int variable.
Java therefore requires:
byte result = (byte) value;26. Another Interview Trap — Arithmetic Promotion#
Now consider:
byte a = 10;
byte b = 20;
byte result = a + b;Will it compile?
No.
Why?
When Java performs many arithmetic operations on byte, short, and char, the operands undergo numeric promotion to int.
Conceptually:
byte a
\
→ int + int → int
/
byte bTherefore:
a + bhas type int.
This fails:
byte result = a + b;You could write:
byte result = (byte) (a + b);but only if narrowing is genuinely safe for your requirement.
27. Compound Assignment Surprise#
Consider:
byte value = 10;
value += 20;This compiles.
But:
byte value = 10;
value = value + 20;does not compile without a cast.
Why?
A compound assignment such as:
value += 20;includes an implicit conversion back to the type of the left-hand variable.
Conceptually, it behaves similarly to:
value = (byte) (value + 20);with the language's compound-assignment semantics.
This also means overflow can occur silently:
byte value = 120;
value += 20;
System.out.println(value);Output:
-116So convenience does not mean safety.
28. Overflow#
Imagine that the largest int value has already been reached.
int value = Integer.MAX_VALUE;
System.out.println(value);Output:
2147483647Now add one:
value++;You might expect a bigger positive number.
But int has no larger representable value.
The result wraps around:
-2147483648Example:
public class Main {
public static void main(String[] args) {
int value = Integer.MAX_VALUE;
value++;
System.out.println(value);
}
}Output:
-2147483648This is integer overflow.
29. Integer Underflow#
The opposite boundary behaves similarly.
int value = Integer.MIN_VALUE;
value--;
System.out.println(value);Output:
2147483647You crossed below the minimum representable int value, so the fixed-width integer representation wraps.
30. Detecting Integer Arithmetic Overflow#
Production code sometimes needs checked arithmetic.
Instead of:
int result = a + b;when overflow must be rejected, you can use:
int result = Math.addExact(a, b);Similarly:
Math.subtractExact(a, b);
Math.multiplyExact(a, b);
Math.incrementExact(a);
Math.decrementExact(a);
Math.toIntExact(longValue);These methods throw ArithmeticException when the exact result cannot be represented.
31. Floating-Point Overflow Is Different#
Floating-point types behave differently.
Consider:
float value = Float.MAX_VALUE;
float result = value * 2;
System.out.println(result);The result can become:
InfinitySimilarly, a large negative result can become:
-InfinityThat is different from integer wraparound.
32. Floating-Point Underflow#
Very tiny nonzero floating-point values can become subnormal numbers and, eventually, zero when the target floating-point representation cannot preserve them.
Example:
double value = 1e-50;
float result = (float) value;
System.out.println(result);Result:
0.0The Java specification explicitly gives this kind of narrowing example: a sufficiently small nonzero double can become 0.0f, while a sufficiently large double can become infinity when narrowed to float.
33. Special Floating-Point → Integer Rules#
These rules are worth knowing for interviews.
Fractional value#
System.out.println((int) 12.9);Output:
12Negative fractional value#
System.out.println((int) -12.9);Output:
-12NaN#
System.out.println((int) Double.NaN);Output:
0Too large positive value#
System.out.println((int) 1e100);Output:
2147483647Too large negative value#
System.out.println((int) -1e100);Output:
-2147483648For floating-point-to-integral narrowing, Java rounds toward zero; NaN converts to zero at the first integral-conversion step, and out-of-range magnitude is clamped to the relevant int or long boundary before any further narrowing.
34. One Important Distinction#
These two conversions do not behave the same way:
long huge = 5_000_000_000L;
int x = (int) huge;and:
double huge = 5_000_000_000.0;
int x = (int) huge;For:
long → inthigh-order bits are discarded.
For:
double → intJava follows floating-point-to-integral narrowing rules, including rounding toward zero and saturation at the integral boundary when the intermediate value is out of range.
This is an excellent interview distinction.
35. Primitive to String Conversion#
Until now we have converted one numeric representation to another.
But real applications constantly cross another boundary.
An HTTP parameter, configuration value, CSV field, database text field, form input, or log message may represent numbers as text.
For example:
"123"This is not the same as:
123The first is a String.
The second is an int.
36. Convert Primitive to String Using String.valueOf()#
This is one of the most readable approaches.
public class Main {
public static void main(String[] args) {
int age = 35;
String text = String.valueOf(age);
System.out.println(text);
}
}Result:
35But now:
texthas type:
String36.1 Other Primitive Types#
int intValue = 100;
long longValue = 5000L;
double doubleValue = 99.99;
boolean active = true;
char grade = 'A';
String a = String.valueOf(intValue);
String b = String.valueOf(longValue);
String c = String.valueOf(doubleValue);
String d = String.valueOf(active);
String e = String.valueOf(grade);37. Primitive to String Using Wrapper toString()#
Example:
int number = 100;
String text = Integer.toString(number);For a double:
double number = 10.5;
String text = Double.toString(number);For a boolean:
boolean value = true;
String text = Boolean.toString(value);38. String Concatenation#
You will also see:
int age = 35;
String text = "" + age;It works.
But for deliberate conversion, this:
String.valueOf(age)or:
Integer.toString(age)usually communicates intent more clearly.
Do not write code merely because it is shorter.
Write code whose purpose is obvious.
39. String to Primitive Conversion#
Now imagine that your application receives:
String ageText = "35";You want to calculate:
age + 1You cannot meaningfully perform numeric arithmetic while the value remains textual.
You need parsing.
40. Integer.parseInt()#
public class Main {
public static void main(String[] args) {
String text = "35";
int age = Integer.parseInt(text);
System.out.println(age + 1);
}
}Output:
36The method:
Integer.parseInt(...)takes textual integer representation and returns primitive:
intOracle documents parseInt(String) as parsing a signed decimal integer and returning primitive int.
41. Double.parseDouble()#
public class Main {
public static void main(String[] args) {
String text = "19.75";
double value = Double.parseDouble(text);
System.out.println(value * 2);
}
}Output:
39.5Double.parseDouble(String) returns primitive double.
42. Common String Parsing Methods#
| String contains | Typical Method | Result |
|---|---|---|
| byte | Byte.parseByte(text) | byte |
| short | Short.parseShort(text) | short |
| int | Integer.parseInt(text) | int |
| long | Long.parseLong(text) | long |
| float | Float.parseFloat(text) | float |
| double | Double.parseDouble(text) | double |
| boolean | Boolean.parseBoolean(text) | boolean |
There is no:
Character.parseChar(...)for extracting a single Java char.
For a one-character String:
String text = "A";
char value = text.charAt(0);But production code should first verify that the string is non-null and has the expected length.
43. What Happens for Invalid Numeric Text?#
Consider:
String text = "hello";
int number = Integer.parseInt(text);Java cannot interpret "hello" as a decimal integer.
It throws:
NumberFormatExceptionLikewise:
Integer.parseInt("12.5");throws NumberFormatException, because "12.5" is not a valid decimal int representation.
44. Production Parsing#
Risky#
int age = Integer.parseInt(userInput);If userInput is invalid, your request flow may fail.
More Robust#
public class Main {
public static void main(String[] args) {
String userInput = "35";
try {
int age = Integer.parseInt(userInput);
if (age < 0) {
System.out.println("Age cannot be negative");
return;
}
System.out.println("Age: " + age);
} catch (NumberFormatException exception) {
System.out.println("Age must be a valid integer");
}
}
}Notice that two kinds of validation are happening:
Parsing validation
"abc" is not an integer
↓
Business validation
-5 may parse successfully,
but negative age is invalidParsing and business validation are different responsibilities.
45. Whitespace Trap#
Do not automatically assume this works:
Integer.parseInt(" 123 ")For normal integer parsing, whitespace is not automatically accepted as part of the numeric representation.
A common approach is:
String text = " 123 ";
int number = Integer.parseInt(text.trim());In modern Java, strip() may be preferable when Unicode-aware whitespace handling is relevant:
int number = Integer.parseInt(text.strip());Choose based on your Java version and input requirements.
46. parseInt() With a Radix#
A radix means numeric base.
Decimal:
base 10Binary:
base 2Hexadecimal:
base 16Example:
public class Main {
public static void main(String[] args) {
int value = Integer.parseInt("1010", 2);
System.out.println(value);
}
}Output:
10Another:
int value = Integer.parseInt("FF", 16);
System.out.println(value);Output:
255Oracle's Integer API defines overloads of parseInt for parsing values in a supplied radix.
47. valueOf()#
Now another method appears:
Integer.valueOf("123")You may ask:
IfparseInt()already converts the string, why do we needvalueOf()?
Because the return type is different.
48. parseInt() vs valueOf()#
int a = Integer.parseInt("100");
Integer b = Integer.valueOf("100");Types:
parseInt()
↓
primitive int
valueOf()
↓
Integer objectOracle documents Integer.valueOf(String) as returning an Integer object representing the parsed value, whereas parseInt(String) returns primitive int.
Comparison:
| Feature | Integer.parseInt() | Integer.valueOf() |
|---|---|---|
| Input | String | String |
| Main output | int | Integer |
| Primitive result | Yes | No |
| Wrapper result | No | Yes |
| Invalid numeric text | NumberFormatException | NumberFormatException |
49. Why Wrapper Objects Exist#
A new concept has appeared.
Java has primitive types:
int
double
boolean
charand corresponding wrapper classes:
Integer
Double
Boolean
CharacterCommon pairs:
| Primitive | Wrapper |
|---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
Wrappers matter because many Java APIs and generic collections work with objects.
For example:
List<Integer>not:
List<int>50. Boxing#
Conversion:
primitive
↓
wrapper objectis called boxing.
Example:
int number = 10;
Integer object = Integer.valueOf(number);Java can also perform autoboxing:
int number = 10;
Integer object = number;51. Unboxing#
The reverse:
wrapper
↓
primitiveis called unboxing.
Example:
Integer object = Integer.valueOf(10);
int number = object.intValue();Java can perform automatic unboxing:
Integer object = 10;
int number = object;52. Wrapper Conversion Methods#
Wrapper classes inherit numeric conversion methods from Number where appropriate.
For example:
Integer value = 100;
byte a = value.byteValue();
short b = value.shortValue();
int c = value.intValue();
long d = value.longValue();
float e = value.floatValue();
double f = value.doubleValue();Similarly:
Double value = 123.75;
int a = value.intValue();
long b = value.longValue();
float c = value.floatValue();
double d = value.doubleValue();These conversions ultimately follow applicable primitive conversion behavior.
53. Autounboxing null Trap#
Consider:
Integer number = null;
int value = number;The compiler allows the assignment because unboxing is supported.
But at runtime Java needs to obtain the primitive value from the object.
There is no object.
Result:
NullPointerExceptionThis is a major production trap.
Risky#
Integer quantity = repositoryResult;
int total = quantity;Safer when null is legitimate#
Integer quantity = repositoryResult;
int total = quantity != null ? quantity : 0;But whether 0 is the correct default depends entirely on the business requirement.
Do not hide missing data by blindly replacing every null with zero.
54. Avoid Deprecated Wrapper Constructors#
Older Java code may contain:
Integer number = new Integer(10);Modern Java code should generally use:
Integer number = Integer.valueOf(10);or autoboxing:
Integer number = 10;Oracle's current API marks Integer(int) and Integer(String) constructors as deprecated since Java 9 and recommends valueOf() or parsing methods instead.
55. Integer Caching — Interview Trap#
Consider:
Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);
System.out.println(a == b);Typically:
trueJava guarantees caching for Integer.valueOf() values from:
-128 to 127and implementations may cache additional values.
But never use:
==to compare wrapper numeric values.
Use:
a.equals(b)Why?
== between references asks whether they refer to the same object.
.equals() asks whether the wrapper values are equal.
56. Reference Type Casting#
Until now our variables contained primitive values.
Java also has reference types.
Example:
class Animal {
}
class Dog extends Animal {
}Dog is a subtype of Animal.
Now consider:
Dog dog = new Dog();
Animal animal = dog;What happened?
The Dog object did not physically transform into an Animal object.
The same Dog object is now being referenced through a variable whose declared type is Animal.
That is reference conversion.
57. Upcasting#
When a subtype reference is treated as one of its supertypes, we commonly call it upcasting.
Example:
Dog dog = new Dog();
Animal animal = dog;Conceptually:
Dog
↓
AnimalBecause every Dog is an Animal, this is safe.
Java therefore allows it implicitly.
The Java Language Specification describes widening reference conversion as treating a subtype reference as a supertype reference and notes that such widening reference conversion does not itself throw a runtime exception.
58. Why Upcasting Is Useful#
Suppose:
class Animal {
void makeSound() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Dog barks");
}
}Now:
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
animal.makeSound();
}
}Output:
Dog barksThe reference type is:
Animalbut the actual object is:
DogAt runtime, overridden instance-method dispatch selects the implementation belonging to the actual object's class.
This is one of the foundations of polymorphism.
59. Reference Type vs Actual Object Type#
You must keep these two ideas separate.
Animal animal = new Dog();Reference / declared type#
AnimalRuntime object type#
DogThe reference type influences which members are accessible at compile time.
The actual runtime object influences dynamic dispatch of overridden instance methods.
60. What Upcasting Does Not Do#
Suppose:
class Dog extends Animal {
void fetch() {
System.out.println("Fetching");
}
}Then:
Animal animal = new Dog();
animal.fetch();does not compile.
Why?
At compile time, Java examines the declared reference type:
Animaland Animal does not declare fetch().
The fact that the actual object happens to be a Dog does not make arbitrary Dog-specific members accessible through an Animal reference.
61. Then How Do We Access Dog-Specific Behavior?#
We may need to convert the reference back.
That introduces downcasting.
62. Downcasting#
When a supertype reference is explicitly treated as a subtype reference, we commonly call it downcasting.
Example:
Animal animal = new Dog();
Dog dog = (Dog) animal;
dog.fetch();Here:
Animal reference
↓
explicit cast
↓
Dog referenceThe object itself was already a Dog.
We are changing how the reference is treated.
63. Why Downcasting Needs an Explicit Cast#
Consider:
Animal animal = new Animal();
Dog dog = (Dog) animal;The compiler can see that an Animal reference might legally refer to a Dog.
But it cannot assume that this particular runtime object actually is a Dog.
At runtime the JVM checks the cast.
Here the object is only an Animal.
Result:
ClassCastExceptionJava's specification states that a checked narrowing reference conversion can throw ClassCastException when the runtime object cannot satisfy the target type.
64. Wrong Downcast#
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
public class Main {
public static void main(String[] args) {
Animal animal = new Cat();
Dog dog = (Dog) animal;
}
}The reference variable can hold any compatible Animal object.
Here it holds:
CatCasting that reference to:
Dogfails at runtime.
65. We Need a Safe Question Before Downcasting#
We need a way to ask:
Is this object actually compatible with Dog?Java provides exactly such an operator.
66. instanceof#
Classic syntax:
objectReference instanceof TypeExample:
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.fetch();
}Complete example:
class Animal {
}
class Dog extends Animal {
void fetch() {
System.out.println("Dog is fetching");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.fetch();
}
}
}Output:
Dog is fetching67. instanceof With null#
Consider:
Animal animal = null;
System.out.println(animal instanceof Dog);Result:
falseinstanceof does not throw NullPointerException merely because the tested reference is null.
That makes it convenient for safe type testing.
68. Casting null#
This is another useful distinction.
Animal animal = null;
Dog dog = (Dog) animal;This cast itself results in:
dog == nullIt does not throw ClassCastException.
But doing this afterward:
dog.fetch();throws NullPointerException.
So remember:
invalid runtime object type
↓
ClassCastException
null reference dereference
↓
NullPointerExceptionThey are different failures.
69. Classic instanceof Has Repetition#
Look at the pattern:
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.fetch();
}We first ask:
animal instanceof DogThen immediately repeat:
(Dog) animalThe compiler already knows that the branch is entered only when the type test succeeds.
So developers repeatedly had to write information Java already knew.
A newer language feature reduces that repetition.
70. Pattern Matching With instanceof#
From Java 16 onward, pattern matching for instanceof is a permanent language feature. It was previewed in Java 14 and 15 before becoming final in Java 16.
Classic form:
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.fetch();
}Java 16+ pattern form:
if (animal instanceof Dog dog) {
dog.fetch();
}This expression:
animal instanceof Dog dogdoes two useful things:
1. Tests whether animal is a Dog
2. If true, introduces Dog variable dog71. Pattern Variable Scope#
Consider:
if (animal instanceof Dog dog) {
dog.fetch();
}dog is available only where Java's flow analysis knows that the match succeeded.
You cannot generally write:
if (animal instanceof Dog dog) {
dog.fetch();
}
dog.fetch();because outside the valid scope Java can no longer guarantee that dog was created by a successful match.
72. Pattern Matching With Additional Condition#
This is valid:
if (animal instanceof Dog dog && dog.isTrained()) {
System.out.println("Trained dog");
}Why can the right side use:
dog?
Because && short-circuits.
Java evaluates the right side only if the left side is true.
Therefore, when evaluating:
dog.isTrained()the pattern has already matched.
73. Why This Does Not Work the Same Way With ||#
Conceptually:
if (animal instanceof Dog dog || dog.isTrained()) {
}is problematic.
If the first condition is false, Java may need to evaluate the second side.
But in that path there is no successfully matched Dog dog.
Therefore the pattern variable cannot safely be used there.
This is flow-sensitive scoping.
74. Classic vs Pattern instanceof#
| Dimension | Classic | Java 16+ Pattern Matching |
|---|---|---|
| Test type | Yes | Yes |
| Explicit cast afterward | Required | Usually unnecessary |
| Repetition | More | Less |
| Local variable declaration | Separate | Included in pattern |
| Readability | Good | Often better |
| Java version | Long-standing | Permanent since Java 16 |
Decision rule#
If your project runs Java 16+ and pattern matching makes the code clearer:
if (obj instanceof Dog dog)is usually preferable.
If your project must compile on Java 8 or Java 11, use the classic form.
75. Primitive Casting vs Reference Casting#
These are related but fundamentally different.
| Dimension | Primitive Casting | Reference Casting |
|---|---|---|
| Concern | Numeric/value representation | Reference type compatibility |
| Example | double → int | Animal → Dog |
| Can change numeric value? | Yes | Object itself is not numerically transformed |
| Common risk | Precision/range loss | ClassCastException |
| Safety tool | Range checks / exact arithmetic | instanceof |
| Example syntax | (int) price | (Dog) animal |
76. Conversion vs Parsing#
Another common confusion:
double value = 10.5;
int number = (int) value;is a numeric conversion.
But:
String text = "10";
int number = Integer.parseInt(text);is parsing.
A string contains textual characters representing a number.
Java has to interpret that text according to a grammar.
That is conceptually different from converting an already numeric primitive representation.
77. Parsing vs Casting — Critical Rule#
This is invalid:
String text = "100";
int value = (int) text;A String object cannot simply be cast to primitive int.
Use:
int value = Integer.parseInt(text);Similarly, this is not parsing:
double value = 100.5;
int result = (int) value;That is primitive narrowing.
78. Common Mistakes#
Mistake 1 — Assuming widening never loses precision#
Why developers make it#
They learn:
small type → big type = safe.
Why risky#
int → float, long → float, and long → double may lose precision.
Preferred approach#
Know the difference between range and exact precision.
Interview connection#
A strong answer is:
Widening primitive conversion does not necessarily preserve exact precision. For example, some largeintvalues cannot be represented exactly asfloat.
Mistake 2 — Assuming casts round floating values#
Wrong assumption#
(int) 9.9becomes:
10Actual result#
9Correct approach#
Use explicit rounding logic when rounding is the requirement.
Mistake 3 — Blindly narrowing integers#
Risky#
byte quantity = (byte) userValue;Consequence#
Out-of-range values silently change.
Preferred approach#
Validate the range first.
Mistake 4 — Treating '5' as integer 5#
Risky#
int number = '5';Result:
53Preferred#
int number = Character.digit('5', 10);or, for a validated ASCII decimal digit:
int number = '5' - '0';Mistake 5 — Parsing without validation#
Risky#
int amount = Integer.parseInt(requestValue);Failure#
Invalid text produces NumberFormatException.
Production approach#
Validate input and define a meaningful error path.
Mistake 6 — Using valueOf() when primitive result is all you need without understanding the distinction#
Integer value = Integer.valueOf("100");is correct.
But if your requirement specifically needs primitive int:
int value = Integer.parseInt("100");communicates that intent directly.
Mistake 7 — Unsafe downcasting#
Risky#
Dog dog = (Dog) animal;without knowing the runtime type.
Consequence#
ClassCastException.
Preferred#
Design APIs so repeated downcasting is minimized.
Where necessary:
if (animal instanceof Dog dog) {
dog.fetch();
}on Java 16+.
Mistake 8 — Confusing reference type with object type#
Animal animal = new Dog();does not mean the object became an Animal object.
The runtime object remains a Dog.
Mistake 9 — Autounboxing null#
Integer number = null;
int value = number;produces NullPointerException.
Mistake 10 — Using == for wrapper value comparison#
Risky#
Integer a = 1000;
Integer b = 1000;
System.out.println(a == b);Do not depend on object identity.
Use:
a.equals(b)when both references are known non-null, or use an appropriate null-safe comparison strategy.
79. Edge Cases and Traps#
Edge Case — Narrowing 255 to byte#
System.out.println((byte) 255);Output:
-1Edge Case — Double.NaN to int#
System.out.println((int) Double.NaN);Output:
0Edge Case — Positive infinity to int#
System.out.println((int) Double.POSITIVE_INFINITY);Output:
2147483647Edge Case — Negative infinity to int#
System.out.println((int) Double.NEGATIVE_INFINITY);Output:
-2147483648Edge Case — null instanceof Type#
Object value = null;
System.out.println(value instanceof String);Output:
falseEdge Case — Cast null#
Object value = null;
String text = (String) value;
System.out.println(text);Output:
nullEdge Case — Valid reference cast#
Object value = "Java";
String text = (String) value;Works because the runtime object is actually a String.
Edge Case — Invalid reference cast#
Object value = Integer.valueOf(10);
String text = (String) value;Throws:
ClassCastException80. Production Decision Rules#
Use this mental decision tree:
Do I already have a numeric primitive?
│
├── Need a wider compatible primitive?
│ └── Use normal widening conversion
│
├── Need a narrower primitive?
│ ├── Is information loss acceptable?
│ │ ├── Yes → explicit cast
│ │ └── No → validate / checked conversion
│ └── For long → int requiring exactness
│ └── Math.toIntExact()
│
├── Need text?
│ └── String.valueOf() / appropriate toString()
│
└── Have text and need a number?
└── parseXxx() + error handling
Have a reference type?
│
├── Subtype → supertype
│ └── Upcasting, normally implicit
│
└── Supertype → subtype
├── Runtime type guaranteed by design?
│ └── Explicit cast may be appropriate
└── Not guaranteed?
└── Test with instanceof / redesign API81. When Casting Is a Design Smell#
Casting itself is not bad.
But repeated reference casts like:
if (employee instanceof Developer) {
Developer developer = (Developer) employee;
}
if (employee instanceof Manager) {
Manager manager = (Manager) employee;
}throughout a codebase can indicate that polymorphism is not being used effectively.
Sometimes the better design is:
employee.calculateBonus();with subtype-specific overridden behavior.
The lesson is not:
Never cast.
The lesson is:
Cast when conversion is truly the requirement, not as a substitute for good object-oriented design.
82. Financial Calculation Warning#
Suppose:
double price = 0.1;
double total = price * 3;
System.out.println(total);You may observe floating-point representation effects.
For monetary calculations requiring exact decimal behavior, production systems commonly use:
BigDecimalrather than solving precision problems by repeatedly casting between primitive numeric types.
BigDecimal is a larger topic, so the important boundary for this chapter is simply:
Primitive numeric casting does not create decimal financial precision.
83. Complete Comparison#
| Concept | Direction / Purpose | Automatic? | Main Risk |
|---|---|---|---|
| Widening primitive | Smaller compatible numeric type → wider type | Usually yes | Possible precision loss |
| Narrowing primitive | Wider numeric type → narrower type | Usually explicit | Range/precision loss |
Primitive → String | Value → text | Via method/context | Formatting assumptions |
String → primitive | Text → numeric value | Parsing method | NumberFormatException |
| Boxing | Primitive → wrapper | Can be automatic | Allocation/identity/null semantics |
| Unboxing | Wrapper → primitive | Can be automatic | NullPointerException |
| Upcasting | Subtype reference → supertype | Usually yes | Loss of subtype-specific compile-time access |
| Downcasting | Supertype reference → subtype | Explicit | ClassCastException |
instanceof | Runtime type compatibility test | N/A | Poor design if overused |
Pattern instanceof | Test + pattern variable | Java 16+ | Version compatibility |
84. Complete Revision#
One-Line Definitions#
Type Conversion: Changing or interpreting a value/reference from one type as another compatible type.
Implicit Conversion: Conversion Java performs without an explicit cast expression.
Widening Primitive Conversion: A defined primitive conversion toward a type capable of representing a wider numeric range.
Explicit Conversion: Conversion specifically requested by the programmer.
Narrowing Primitive Conversion: A conversion that may reduce range or precision and generally requires a cast.
Casting: Using syntax such as (int) or (Dog) to request a conversion permitted in a casting context.
Upcasting: Treating a subtype reference as a supertype reference.
Downcasting: Treating a supertype reference as a subtype reference.
instanceof: Tests whether a reference's runtime value is compatible with a specified reference type/pattern.
Parsing: Interpreting textual data as another value, such as "123" → 123.
Boxing: Primitive → wrapper.
Unboxing: Wrapper → primitive.
85. Syntax Revision#
Widening#
int value = 100;
long result = value;Narrowing#
double value = 10.9;
int result = (int) value;Primitive → String#
String text = String.valueOf(value);String → int#
int value = Integer.parseInt(text);String → double#
double value = Double.parseDouble(text);String → Integer#
Integer value = Integer.valueOf(text);Upcasting#
Animal animal = new Dog();Downcasting#
Dog dog = (Dog) animal;Classic safe check#
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
}Java 16+#
if (animal instanceof Dog dog) {
dog.fetch();
}86. If You Remember Only 10 Things#
- Widening is usually automatic; narrowing usually requires an explicit cast.
- Widening does not guarantee exact precision.
- Floating-point → integer casting truncates toward zero; it does not perform normal rounding.
- Narrowing integral types can silently change values because high-order bits may be discarded.
- Integer arithmetic overflow wraps; Java does not automatically throw an exception for normal
+,-, or*. - Use parsing, not casting, for
"123"→123. parseInt()returnsint;valueOf()returnsInteger.- Upcasting is normally safe and implicit.
- Invalid downcasting can throw
ClassCastException. - Pattern matching for
instanceofremoves the redundant explicit cast in Java 16+.
87. Final Knowledge Map#
Java Type Conversion & Casting
│
├── Primitive Conversion
│ │
│ ├── Implicit Conversion
│ │ └── Widening
│ │ ├── byte
│ │ ├── short
│ │ ├── char
│ │ ├── int
│ │ ├── long
│ │ ├── float
│ │ └── double
│ │
│ ├── Explicit Conversion
│ │ └── Narrowing
│ │ ├── Fraction loss
│ │ ├── Range loss
│ │ ├── Precision loss
│ │ └── Bit truncation
│ │
│ ├── Numeric Promotion
│ │ ├── byte/short/char → int
│ │ └── Arithmetic expressions
│ │
│ ├── char Conversion
│ │ ├── char → numeric
│ │ └── numeric → char
│ │
│ ├── Overflow
│ │ ├── Integer wraparound
│ │ └── Floating Infinity
│ │
│ └── Underflow
│ └── Tiny floating value → subnormal/zero
│
├── String Conversion
│ │
│ ├── Primitive → String
│ │ ├── String.valueOf()
│ │ └── Xxx.toString()
│ │
│ └── String → Primitive / Wrapper
│ ├── parseInt()
│ ├── parseDouble()
│ ├── parseXxx()
│ └── valueOf()
│
├── Wrapper Conversion
│ ├── Boxing
│ ├── Unboxing
│ ├── xxxValue()
│ └── null trap
│
└── Reference Conversion
│
├── Upcasting
│ └── subtype → supertype
│
├── Downcasting
│ └── supertype → subtype
│
├── ClassCastException
│
├── instanceof
│
└── Pattern Matching instanceof
└── Java 16+