Skip to lesson
CodeLangs AISoftware Training Institute
Type Conversion and Type Casting in Java

Chapter 4 · Java Type System

Type Conversion and Type Casting in Java

Master Java type conversion and casting, including primitive widening and narrowing, numeric promotion, parsing, boxing, reference casting, instanceof pattern matching, overflow risks, and production-safe conversion decisions.

  • 8,754words
  • 40min read
  • 20quiz items
  • 16practice tools

Imagine that you are building an employee payroll application.

You receive an employee's age as an int:

Java
int age = 35;

Later, some calculation requires a long.

Java
long employeeAge = age;

Java accepts this without any special syntax.

But now suppose you have a salary stored as a double:

Java
double salary = 45678.75;

and you try:

Java
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:

TypeSizeGeneral Role
byte8 bitsVery small integers
short16 bitsSmall integers
int32 bitsNormal integer calculations
long64 bitsLarge integers
float32 bitsFloating-point values
double64 bitsHigher-precision floating-point values
char16 bitsUTF-16 code unit / unsigned numeric value
booleanJVM-dependent representationtrue or false

Now think about this:

Java
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:

Java
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:

Java
int number = 100;
double result = number;

Here:

Output
int
 ↓
double

The value originally belongs to the int type, but Java converts it to double.

Output:

Output
100.0

Type conversions broadly appear in two forms:

Output
Type Conversion
│
├── Automatic / Implicit Conversion
│      └── Usually widening
│
└── Explicit Conversion
       └── Usually narrowing / casting

Do not memorize those words yet.

We will build them naturally.


3. Why Does Java Sometimes Convert Automatically?#

Consider:

Java
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:

Output
Small container
      ↓
Larger compatible container

Java 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:

Java
public class Main {
    public static void main(String[] args) {
        int number = 100;
        long result = number;

        System.out.println(result);
    }
}

Output:

Output
100

There is no cast:

Java
(long) number

Java 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:

Output
byte
 ├── short
 ├── int
 ├── long
 ├── float
 └── double

short
 ├── int
 ├── long
 ├── float
 └── double

char
 ├── int
 ├── long
 ├── float
 └── double

int
 ├── long
 ├── float
 └── double

long
 ├── float
 └── double

float
 └── double

These are the widening primitive conversions defined by the Java Language Specification.

A common simplified memory chain is:

Output
byte → short → int → long → float → double

But be careful: char is separate.

Output
char → int → long → float → double

char does not widen to short.


5.1 Small Example#

Java
public class Main {
    public static void main(String[] args) {
        byte value = 10;
        int result = value;

        System.out.println(result);
    }
}

Output:

Output
10

Execution:

Output
byte value = 10
      ↓
Java sees int destination
      ↓
byte → int is valid widening
      ↓
conversion happens automatically
      ↓
result = 10

5.2 Multiple Widening Conversions#

Java
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:

Output
100
100
100
100.0
100.0

6. 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:

Java
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:

Java
int → float

is 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#

Output
Wider range ≠ always greater exact precision

That 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:

Java
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:

Output
16777217
1.6777216E7

The 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:

Java
double price = 199.99;
int amount = price;

What problems could occur?

First:

Output
199.99

contains a fractional portion.

int cannot represent .99.

Second, double can represent magnitudes far beyond the int range.

So converting:

Output
double
  ↓
int

can 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:

Java
(targetType) value

Example:

Java
double price = 199.99;
int amount = (int) price;

Here:

Java
(int)

is the cast operator.

Complete program:

Java
public class Main {
    public static void main(String[] args) {
        double price = 199.99;
        int amount = (int) price;

        System.out.println(amount);
    }
}

Output:

Output
199

Notice 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:

Output
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 → short

The Java Language Specification defines narrowing conversions as conversions that can lose magnitude, range, or precision.


11. Basic Narrowing Example#

Java
public class Main {
    public static void main(String[] args) {
        double number = 45.98;
        int result = (int) number;

        System.out.println(result);
    }
}

Output:

Output
45

Execution:

Output
45.98
  ↓
cast to int
  ↓
fractional portion removed
  ↓
45

12. Casting Is Not Rounding#

This is worth reinforcing.

Java
double value = 9.99;
int result = (int) value;

Result:

Output
9

Not:

Output
10

Likewise:

Java
double value = -9.99;
int result = (int) value;

Result:

Output
-9

Java rounds toward zero when converting floating-point values to integral values.

Conceptually:

Output
  9.99 →  9
 -9.99 → -9

13. If You Actually Want Rounding#

Then casting alone is not enough.

For example:

Java
public class Main {
    public static void main(String[] args) {
        double value = 9.99;

        long rounded = Math.round(value);

        System.out.println(rounded);
    }
}

Output:

Output
10

So these solve different problems:

Java
(int) value

means:

Convert to int, discarding the fractional portion according to Java's narrowing rules.

Whereas:

Java
Math.round(value)

means:

Perform rounding.

14. Numeric Type Conversion#

Now that widening and narrowing are clear, let us connect the numeric types.

Widening#

Java
byte value = 10;
short a = value;
int b = value;
long c = value;
float d = value;
double e = value;

Usually no cast is required.

Narrowing#

Java
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:

Java
int number = 130;
byte result = (byte) number;

System.out.println(result);

You might predict:

Output
130

But a byte can only represent:

Output
-128 to 127

So 130 does not fit.

The output is:

Output
-126

Why?

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:

Output
130
binary form:
00000000 00000000 00000000 10000010

Keep only lowest 8 bits:

10000010

As a signed Java byte, that bit pattern represents:

Output
-126

16. Another Data-Loss Example#

Java
public class Main {
    public static void main(String[] args) {
        int number = 255;
        byte result = (byte) number;

        System.out.println(result);
    }
}

Output:

Output
-1

Oracle'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.

Java
int number = 1000;
byte result = (byte) number;

Java does not automatically throw:

Output
ArithmeticException

or:

Output
NumberFormatException

It 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:

Java
byte result = (byte) value;

Instead:

Java
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:

Output
long → int

but you do not want silent overflow.

Instead of:

Java
long value = 5_000_000_000L;
int result = (int) value;

you can use:

Java
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:

Java
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:

Java
public class Main {
    public static void main(String[] args) {
        char letter = 'A';
        int value = letter;

        System.out.println(value);
    }
}

Output:

Output
65

The numeric value associated with 'A' here is 65.

Likewise:

Java
char letter = 'a';
int value = letter;

produces:

Output
97

22. Numeric Value to char#

Now reverse the idea.

Java
public class Main {
    public static void main(String[] args) {
        int value = 65;
        char character = (char) value;

        System.out.println(character);
    }
}

Output:

Output
A

Notice that the cast is needed:

Java
(char) value

because:

Output
int → char

is a narrowing conversion.


23. Common Mistake — Digit Character vs Numeric Digit#

This is extremely common.

Suppose:

Java
char digit = '5';

What does this produce?

Java
int number = digit;

It does not produce numeric 5.

It produces the UTF-16 value of '5', which is:

Output
53

Example:

Java
public class Main {
    public static void main(String[] args) {
        char digit = '5';
        int number = digit;

        System.out.println(number);
    }
}

Output:

Output
53

24. Converting '5' to Numeric 5#

For decimal digits:

Java
char digit = '5';
int number = digit - '0';

System.out.println(number);

Output:

Output
5

Why?

Output
'5' = 53
'0' = 48

53 - 48 = 5

A more semantic approach is:

Java
int number = Character.getNumericValue(digit);

For validating decimal digits, you might also use:

Java
Character.isDigit(digit)

depending on the requirement.


25. Compile-Time Constant Conversion — A Very Important Interview Trap#

Look at this:

Java
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:

Java
byte a = 100;
short b = 1000;
char c = 65;

But this does not:

Java
byte a = 200;

because 200 does not fit in a byte.

And this is different:

Java
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:

Java
byte result = (byte) value;

26. Another Interview Trap — Arithmetic Promotion#

Now consider:

Java
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:

Output
byte a
   \
    → int + int → int
   /
byte b

Therefore:

Java
a + b

has type int.

This fails:

Java
byte result = a + b;

You could write:

Java
byte result = (byte) (a + b);

but only if narrowing is genuinely safe for your requirement.


27. Compound Assignment Surprise#

Consider:

Java
byte value = 10;
value += 20;

This compiles.

But:

Java
byte value = 10;
value = value + 20;

does not compile without a cast.

Why?

A compound assignment such as:

Java
value += 20;

includes an implicit conversion back to the type of the left-hand variable.

Conceptually, it behaves similarly to:

Java
value = (byte) (value + 20);

with the language's compound-assignment semantics.

This also means overflow can occur silently:

Java
byte value = 120;
value += 20;

System.out.println(value);

Output:

Output
-116

So convenience does not mean safety.


28. Overflow#

Imagine that the largest int value has already been reached.

Java
int value = Integer.MAX_VALUE;

System.out.println(value);

Output:

Output
2147483647

Now add one:

Java
value++;

You might expect a bigger positive number.

But int has no larger representable value.

The result wraps around:

Output
-2147483648

Example:

Java
public class Main {
    public static void main(String[] args) {
        int value = Integer.MAX_VALUE;

        value++;

        System.out.println(value);
    }
}

Output:

Output
-2147483648

This is integer overflow.


29. Integer Underflow#

The opposite boundary behaves similarly.

Java
int value = Integer.MIN_VALUE;
value--;

System.out.println(value);

Output:

Output
2147483647

You 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:

Java
int result = a + b;

when overflow must be rejected, you can use:

Java
int result = Math.addExact(a, b);

Similarly:

Java
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:

Java
float value = Float.MAX_VALUE;
float result = value * 2;

System.out.println(result);

The result can become:

Output
Infinity

Similarly, a large negative result can become:

Output
-Infinity

That 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:

Java
double value = 1e-50;
float result = (float) value;

System.out.println(result);

Result:

Output
0.0

The 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#

Java
System.out.println((int) 12.9);

Output:

Output
12

Negative fractional value#

Java
System.out.println((int) -12.9);

Output:

Output
-12

NaN#

Java
System.out.println((int) Double.NaN);

Output:

Output
0

Too large positive value#

Java
System.out.println((int) 1e100);

Output:

Output
2147483647

Too large negative value#

Java
System.out.println((int) -1e100);

Output:

Output
-2147483648

For 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:

Java
long huge = 5_000_000_000L;
int x = (int) huge;

and:

Java
double huge = 5_000_000_000.0;
int x = (int) huge;

For:

Output
long → int

high-order bits are discarded.

For:

Output
double → int

Java 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:

Output
"123"

This is not the same as:

Java
123

The 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.

Java
public class Main {
    public static void main(String[] args) {
        int age = 35;

        String text = String.valueOf(age);

        System.out.println(text);
    }
}

Result:

Output
35

But now:

Java
text

has type:

Output
String

36.1 Other Primitive Types#

Java
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:

Java
int number = 100;
String text = Integer.toString(number);

For a double:

Java
double number = 10.5;
String text = Double.toString(number);

For a boolean:

Java
boolean value = true;
String text = Boolean.toString(value);

38. String Concatenation#

You will also see:

Java
int age = 35;
String text = "" + age;

It works.

But for deliberate conversion, this:

Java
String.valueOf(age)

or:

Java
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:

Java
String ageText = "35";

You want to calculate:

Java
age + 1

You cannot meaningfully perform numeric arithmetic while the value remains textual.

You need parsing.


40. Integer.parseInt()#

Java
public class Main {
    public static void main(String[] args) {
        String text = "35";

        int age = Integer.parseInt(text);

        System.out.println(age + 1);
    }
}

Output:

Output
36

The method:

Java
Integer.parseInt(...)

takes textual integer representation and returns primitive:

Output
int

Oracle documents parseInt(String) as parsing a signed decimal integer and returning primitive int.


41. Double.parseDouble()#

Java
public class Main {
    public static void main(String[] args) {
        String text = "19.75";

        double value = Double.parseDouble(text);

        System.out.println(value * 2);
    }
}

Output:

Output
39.5

Double.parseDouble(String) returns primitive double.


42. Common String Parsing Methods#

String containsTypical MethodResult
byteByte.parseByte(text)byte
shortShort.parseShort(text)short
intInteger.parseInt(text)int
longLong.parseLong(text)long
floatFloat.parseFloat(text)float
doubleDouble.parseDouble(text)double
booleanBoolean.parseBoolean(text)boolean

There is no:

Java
Character.parseChar(...)

for extracting a single Java char.

For a one-character String:

Java
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:

Java
String text = "hello";
int number = Integer.parseInt(text);

Java cannot interpret "hello" as a decimal integer.

It throws:

Output
NumberFormatException

Likewise:

Java
Integer.parseInt("12.5");

throws NumberFormatException, because "12.5" is not a valid decimal int representation.


44. Production Parsing#

Risky#

Java
int age = Integer.parseInt(userInput);

If userInput is invalid, your request flow may fail.

More Robust#

Java
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:

Output
Parsing validation
"abc" is not an integer
       ↓

Business validation
-5 may parse successfully,
but negative age is invalid

Parsing and business validation are different responsibilities.


45. Whitespace Trap#

Do not automatically assume this works:

Java
Integer.parseInt(" 123 ")

For normal integer parsing, whitespace is not automatically accepted as part of the numeric representation.

A common approach is:

Java
String text = " 123 ";

int number = Integer.parseInt(text.trim());

In modern Java, strip() may be preferable when Unicode-aware whitespace handling is relevant:

Java
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:

Output
base 10

Binary:

Output
base 2

Hexadecimal:

Output
base 16

Example:

Java
public class Main {
    public static void main(String[] args) {
        int value = Integer.parseInt("1010", 2);

        System.out.println(value);
    }
}

Output:

Output
10

Another:

Java
int value = Integer.parseInt("FF", 16);
System.out.println(value);

Output:

Output
255

Oracle's Integer API defines overloads of parseInt for parsing values in a supplied radix.


47. valueOf()#

Now another method appears:

Java
Integer.valueOf("123")

You may ask:

If parseInt() already converts the string, why do we need valueOf()?

Because the return type is different.


48. parseInt() vs valueOf()#

Java
int a = Integer.parseInt("100");
Integer b = Integer.valueOf("100");

Types:

Output
parseInt()
   ↓
primitive int

valueOf()
   ↓
Integer object

Oracle documents Integer.valueOf(String) as returning an Integer object representing the parsed value, whereas parseInt(String) returns primitive int.

Comparison:

FeatureInteger.parseInt()Integer.valueOf()
InputStringString
Main outputintInteger
Primitive resultYesNo
Wrapper resultNoYes
Invalid numeric textNumberFormatExceptionNumberFormatException

49. Why Wrapper Objects Exist#

A new concept has appeared.

Java has primitive types:

Java
int
double
boolean
char

and corresponding wrapper classes:

Java
Integer
Double
Boolean
Character

Common pairs:

PrimitiveWrapper
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

Wrappers matter because many Java APIs and generic collections work with objects.

For example:

Java
List<Integer>

not:

Java
List<int>

50. Boxing#

Conversion:

Output
primitive
   ↓
wrapper object

is called boxing.

Example:

Java
int number = 10;
Integer object = Integer.valueOf(number);

Java can also perform autoboxing:

Java
int number = 10;
Integer object = number;

51. Unboxing#

The reverse:

Output
wrapper
   ↓
primitive

is called unboxing.

Example:

Java
Integer object = Integer.valueOf(10);

int number = object.intValue();

Java can perform automatic unboxing:

Java
Integer object = 10;
int number = object;

52. Wrapper Conversion Methods#

Wrapper classes inherit numeric conversion methods from Number where appropriate.

For example:

Java
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:

Java
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:

Java
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:

Output
NullPointerException

This is a major production trap.

Risky#

Java
Integer quantity = repositoryResult;
int total = quantity;

Safer when null is legitimate#

Java
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:

Java
Integer number = new Integer(10);

Modern Java code should generally use:

Java
Integer number = Integer.valueOf(10);

or autoboxing:

Java
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:

Java
Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);

System.out.println(a == b);

Typically:

Output
true

Java guarantees caching for Integer.valueOf() values from:

Output
-128 to 127

and implementations may cache additional values.

But never use:

Java
==

to compare wrapper numeric values.

Use:

Java
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:

Java
class Animal {
}

class Dog extends Animal {
}

Dog is a subtype of Animal.

Now consider:

Java
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:

Java
Dog dog = new Dog();
Animal animal = dog;

Conceptually:

Output
Dog
 ↓
Animal

Because 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:

Java
class Animal {
    void makeSound() {
        System.out.println("Animal sound");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Dog barks");
    }
}

Now:

Java
public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();

        animal.makeSound();
    }
}

Output:

Output
Dog barks

The reference type is:

Output
Animal

but the actual object is:

Output
Dog

At 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.

Java
Animal animal = new Dog();

Reference / declared type#

Output
Animal

Runtime object type#

Output
Dog

The 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:

Java
class Dog extends Animal {
    void fetch() {
        System.out.println("Fetching");
    }
}

Then:

Java
Animal animal = new Dog();
animal.fetch();

does not compile.

Why?

At compile time, Java examines the declared reference type:

Output
Animal

and 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:

Java
Animal animal = new Dog();

Dog dog = (Dog) animal;

dog.fetch();

Here:

Output
Animal reference
      ↓
explicit cast
      ↓
Dog reference

The object itself was already a Dog.

We are changing how the reference is treated.


63. Why Downcasting Needs an Explicit Cast#

Consider:

Java
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:

Output
ClassCastException

Java's specification states that a checked narrowing reference conversion can throw ClassCastException when the runtime object cannot satisfy the target type.


64. Wrong Downcast#

Java
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:

Output
Cat

Casting that reference to:

Output
Dog

fails 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:

Java
objectReference instanceof Type

Example:

Java
if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
    dog.fetch();
}

Complete example:

Java
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:

Output
Dog is fetching

67. instanceof With null#

Consider:

Java
Animal animal = null;

System.out.println(animal instanceof Dog);

Result:

Output
false

instanceof 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.

Java
Animal animal = null;
Dog dog = (Dog) animal;

This cast itself results in:

Output
dog == null

It does not throw ClassCastException.

But doing this afterward:

Java
dog.fetch();

throws NullPointerException.

So remember:

Output
invalid runtime object type
        ↓
ClassCastException

null reference dereference
        ↓
NullPointerException

They are different failures.


69. Classic instanceof Has Repetition#

Look at the pattern:

Java
if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
    dog.fetch();
}

We first ask:

Java
animal instanceof Dog

Then immediately repeat:

Java
(Dog) animal

The 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:

Java
if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
    dog.fetch();
}

Java 16+ pattern form:

Java
if (animal instanceof Dog dog) {
    dog.fetch();
}

This expression:

Java
animal instanceof Dog dog

does two useful things:

Output
1. Tests whether animal is a Dog
2. If true, introduces Dog variable dog

71. Pattern Variable Scope#

Consider:

Java
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:

Java
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:

Java
if (animal instanceof Dog dog && dog.isTrained()) {
    System.out.println("Trained dog");
}

Why can the right side use:

Java
dog

?

Because && short-circuits.

Java evaluates the right side only if the left side is true.

Therefore, when evaluating:

Java
dog.isTrained()

the pattern has already matched.


73. Why This Does Not Work the Same Way With ||#

Conceptually:

Java
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#

DimensionClassicJava 16+ Pattern Matching
Test typeYesYes
Explicit cast afterwardRequiredUsually unnecessary
RepetitionMoreLess
Local variable declarationSeparateIncluded in pattern
ReadabilityGoodOften better
Java versionLong-standingPermanent since Java 16

Decision rule#

If your project runs Java 16+ and pattern matching makes the code clearer:

Java
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.

DimensionPrimitive CastingReference Casting
ConcernNumeric/value representationReference type compatibility
Exampledouble → intAnimal → Dog
Can change numeric value?YesObject itself is not numerically transformed
Common riskPrecision/range lossClassCastException
Safety toolRange checks / exact arithmeticinstanceof
Example syntax(int) price(Dog) animal

76. Conversion vs Parsing#

Another common confusion:

Java
double value = 10.5;
int number = (int) value;

is a numeric conversion.

But:

Java
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:

Java
String text = "100";
int value = (int) text;

A String object cannot simply be cast to primitive int.

Use:

Java
int value = Integer.parseInt(text);

Similarly, this is not parsing:

Java
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 large int values cannot be represented exactly as float.

Mistake 2 — Assuming casts round floating values#

Wrong assumption#

Java
(int) 9.9

becomes:

Output
10

Actual result#

Output
9

Correct approach#

Use explicit rounding logic when rounding is the requirement.


Mistake 3 — Blindly narrowing integers#

Risky#

Java
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#

Java
int number = '5';

Result:

Output
53

Preferred#

Java
int number = Character.digit('5', 10);

or, for a validated ASCII decimal digit:

Java
int number = '5' - '0';

Mistake 5 — Parsing without validation#

Risky#

Java
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#

Java
Integer value = Integer.valueOf("100");

is correct.

But if your requirement specifically needs primitive int:

Java
int value = Integer.parseInt("100");

communicates that intent directly.


Mistake 7 — Unsafe downcasting#

Risky#

Java
Dog dog = (Dog) animal;

without knowing the runtime type.

Consequence#

ClassCastException.

Preferred#

Design APIs so repeated downcasting is minimized.

Where necessary:

Java
if (animal instanceof Dog dog) {
    dog.fetch();
}

on Java 16+.


Mistake 8 — Confusing reference type with object type#

Java
Animal animal = new Dog();

does not mean the object became an Animal object.

The runtime object remains a Dog.


Mistake 9 — Autounboxing null#

Java
Integer number = null;
int value = number;

produces NullPointerException.


Mistake 10 — Using == for wrapper value comparison#

Risky#

Java
Integer a = 1000;
Integer b = 1000;

System.out.println(a == b);

Do not depend on object identity.

Use:

Java
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#

Java
System.out.println((byte) 255);

Output:

Output
-1

Edge Case — Double.NaN to int#

Java
System.out.println((int) Double.NaN);

Output:

Output
0

Edge Case — Positive infinity to int#

Java
System.out.println((int) Double.POSITIVE_INFINITY);

Output:

Output
2147483647

Edge Case — Negative infinity to int#

Java
System.out.println((int) Double.NEGATIVE_INFINITY);

Output:

Output
-2147483648

Edge Case — null instanceof Type#

Java
Object value = null;

System.out.println(value instanceof String);

Output:

Output
false

Edge Case — Cast null#

Java
Object value = null;
String text = (String) value;

System.out.println(text);

Output:

Output
null

Edge Case — Valid reference cast#

Java
Object value = "Java";

String text = (String) value;

Works because the runtime object is actually a String.


Edge Case — Invalid reference cast#

Java
Object value = Integer.valueOf(10);

String text = (String) value;

Throws:

Output
ClassCastException

80. Production Decision Rules#

Use this mental decision tree:

Output
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 API

81. When Casting Is a Design Smell#

Casting itself is not bad.

But repeated reference casts like:

Java
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:

Java
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:

Java
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:

Java
BigDecimal

rather 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#

ConceptDirection / PurposeAutomatic?Main Risk
Widening primitiveSmaller compatible numeric type → wider typeUsually yesPossible precision loss
Narrowing primitiveWider numeric type → narrower typeUsually explicitRange/precision loss
Primitive → StringValue → textVia method/contextFormatting assumptions
String → primitiveText → numeric valueParsing methodNumberFormatException
BoxingPrimitive → wrapperCan be automaticAllocation/identity/null semantics
UnboxingWrapper → primitiveCan be automaticNullPointerException
UpcastingSubtype reference → supertypeUsually yesLoss of subtype-specific compile-time access
DowncastingSupertype reference → subtypeExplicitClassCastException
instanceofRuntime type compatibility testN/APoor design if overused
Pattern instanceofTest + pattern variableJava 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#

Java
int value = 100;
long result = value;

Narrowing#

Java
double value = 10.9;
int result = (int) value;

Primitive → String#

Java
String text = String.valueOf(value);

String → int#

Java
int value = Integer.parseInt(text);

String → double#

Java
double value = Double.parseDouble(text);

String → Integer#

Java
Integer value = Integer.valueOf(text);

Upcasting#

Java
Animal animal = new Dog();

Downcasting#

Java
Dog dog = (Dog) animal;

Classic safe check#

Java
if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
}

Java 16+#

Java
if (animal instanceof Dog dog) {
    dog.fetch();
}

86. If You Remember Only 10 Things#

  1. Widening is usually automatic; narrowing usually requires an explicit cast.
  2. Widening does not guarantee exact precision.
  3. Floating-point → integer casting truncates toward zero; it does not perform normal rounding.
  4. Narrowing integral types can silently change values because high-order bits may be discarded.
  5. Integer arithmetic overflow wraps; Java does not automatically throw an exception for normal +, -, or *.
  6. Use parsing, not casting, for "123"123.
  7. parseInt() returns int; valueOf() returns Integer.
  8. Upcasting is normally safe and implicit.
  9. Invalid downcasting can throw ClassCastException.
  10. Pattern matching for instanceof removes the redundant explicit cast in Java 16+.

87. Final Knowledge Map#

Output
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+

Practice lab

Prove what you just learned