Java Type Casting

Su Suriya Ravichandran Updated 14 Nov 2026
8 min read ·Lesson 9 of 133

Type casting converts a value from one type to another. Java performs some conversions automatically when no information can be lost, and requires an explicit cast when it can. The rules exist to make every potential loss of data visible in the source code.

Widening Conversion (Implicit)

Converting to a type that can hold every value of the original type is a widening conversion. The compiler does it automatically.

byte -> short -> int -> long -> float -> double
                  ^
                char
int i = 100;
long l = i;        // int to long: automatic
double d = l;      // long to double: automatic
float f = 'A';     // char to float: automatic, f is 65.0

No cast is needed because a long can represent every int, and a double can represent every long in magnitude.

One subtlety: int to float and long to float/double are widening by rule, yet can lose precision. A float has 24 bits of mantissa, so an int above about 16 million may be rounded:

int big = 123_456_789;
float f = big;
System.out.println((int) f);   // 123456792

The conversion is permitted implicitly because the magnitude always fits, but the least significant digits may change. When exact large integers matter, do not route them through float.

Narrowing Conversion (Explicit Cast)

Converting to a type that cannot hold every value of the original is narrowing, and the compiler refuses to do it silently.

double price = 9.99;
int whole = price;           // compile error: possible lossy conversion from double to int
int whole = (int) price;     // allowed: whole is 9

The cast syntax is (TargetType) value. It tells the compiler and the reader that truncation is intended.

Floating point to integer: truncation

Casting a double or float to an integer type discards the fractional part, always toward zero. It does not round.

System.out.println((int) 9.99);    // 9
System.out.println((int) -9.99);   // -9
System.out.println((int) 0.5);     // 0

For rounding, use Math.round, which returns a long for a double argument:

long rounded = Math.round(9.99);          // 10
int roundedInt = (int) Math.round(9.5);   // 10

A value too large for the target type saturates: (int) 1e20 is Integer.MAX_VALUE, (int) Double.NaN is 0.

Integer to a smaller integer: bit truncation

Casting between integer types keeps the low-order bits and discards the rest. The result can look nothing like the original.

int i = 300;
byte b = (byte) i;
System.out.println(b);   // 44

300 in binary is 1 0010 1100. A byte holds eight bits: 0010 1100, which is 44. Values within the target's range survive unchanged; values outside it wrap.

long big = 4_000_000_000L;
int truncated = (int) big;
System.out.println(truncated);   // -294967296

This is why reading a large value into an int without range checking is dangerous. The compiler makes you write the cast, but it cannot know whether the runtime value fits.

char and integer conversions

char is unsigned 16-bit; short and byte are signed. Converting between them is narrowing in both directions.

char c = 'A';
int code = c;                  // widening: 65
char back = (char) code;       // narrowing: 'A'
char next = (char) (c + 1);    // 'B'; the arithmetic produces an int, so a cast is needed

Conversions in Arithmetic

Before any binary arithmetic operation, Java promotes both operands to a common type:

  1. If either is double, both become double.
  2. Otherwise, if either is float, both become float.
  3. Otherwise, if either is long, both become long.
  4. Otherwise, both become int, even if both are byte, short, or char.
byte a = 10, b = 20;
byte sum = a + b;          // compile error: a + b is an int
byte sum = (byte) (a + b); // allowed

int x = 7;
double y = 2;
double result = x / y;     // 3.5: x is promoted to double before dividing
int bad = x / 2;           // 3: both int, integer division

Rule 4 is the one that surprises people. There is no byte addition in the JVM; small types are always computed as int.

Compound assignment hides a cast

+=, -=, *=, and the other compound operators include an implicit cast back to the variable's type:

byte b = 10;
b = b + 5;    // compile error
b += 5;       // allowed: equivalent to b = (byte) (b + 5)

This is convenient but can silently truncate, so be aware of it when the right-hand side can be large.

Casting Between Reference Types

Casting a reference does not change the object. It changes only the static type through which the compiler lets you access it.

Upcasting

Treating an object as its parent type or an interface it implements is always safe and needs no cast:

Dog dog = new Dog();
Animal animal = dog;        // upcast: a Dog is an Animal
Object obj = dog;           // every class extends Object

After the upcast, only Animal's methods are accessible through animal, even though the object is still a Dog. Overridden methods still dispatch to the Dog version at runtime.

Downcasting

Going from a parent type back to a child requires an explicit cast, because the compiler cannot know whether the object really is that subtype:

Animal animal = new Dog();
Dog dog = (Dog) animal;      // allowed: the object really is a Dog

Animal other = new Cat();
Dog notADog = (Dog) other;   // compiles, but throws ClassCastException at runtime

The cast is checked at runtime. If the object is not an instance of the target type, ClassCastException is thrown.

Checking before casting

instanceof tests whether a downcast will succeed:

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

Since Java 16, pattern matching combines the test and the cast:

if (animal instanceof Dog dog) {
    dog.fetch();     // dog is already a Dog here
}

The variable dog is declared and assigned only when the test passes. This is the preferred form; it removes the duplicated type name and the possibility of casting to the wrong type after a correct check.

A cast between unrelated classes is rejected at compile time: (String) new Integer(5) does not compile, because no object can be both. Casts involving interfaces are always allowed at compile time (a subclass might implement the interface), so they are checked at runtime instead.

Converting Between Strings and Numbers

A cast cannot turn text into a number. (int) "42" is a compile error: String and int are unrelated types. Parsing is done with methods on the wrapper classes:

int n = Integer.parseInt("42");
double d = Double.parseDouble("3.14");
long big = Long.parseLong("9000000000");
boolean flag = Boolean.parseBoolean("true");

Malformed input throws NumberFormatException:

Integer.parseInt("42abc");   // NumberFormatException
Integer.parseInt(" 42 ");    // NumberFormatException: whitespace is not trimmed
Integer.parseInt("");        // NumberFormatException

Input from users or files should be trimmed and validated, and the exception caught:

String input = " 42 ";
try {
    int value = Integer.parseInt(input.trim());
    System.out.println("Parsed: " + value);
} catch (NumberFormatException e) {
    System.out.println("Not a valid integer: " + input);
}

Converting a number to text:

String s1 = String.valueOf(42);
String s2 = Integer.toString(42);
String s3 = 42 + "";           // works, but the first two are clearer

Boxing Conversions

Converting between a primitive and its wrapper is a boxing or unboxing conversion, performed automatically:

Integer boxed = 42;        // boxing
int unboxed = boxed;       // unboxing

A cast between a wrapper and a different primitive is not allowed in one step:

Integer i = 42;
long l = i;                // allowed: unbox to int, then widen to long
double d = (double) i;     // allowed: unbox, then widen
Long wrong = i;            // compile error: Integer is not a Long
Long right = (long) i;     // unbox, widen, then box as Long

The wrapper types are distinct classes with no inheritance relationship, so Integer to Long requires going through the primitives.

Where Casts Appear in Real Code

Averaging integers. The cast must happen before the division:

int total = 7, count = 2;
double avg = total / count;             // 3.0: integer division, then widening
double avg = (double) total / count;    // 3.5: cast first, then double division

Reading generic or legacy APIs. Older APIs and some reflection code return Object:

Object raw = map.get("count");
if (raw instanceof Integer count) {
    process(count);
}

Working with bytes. I/O and binary protocols produce int values that must be stored as byte, and bytes that must be read as unsigned:

byte b = (byte) 200;                 // stores -56
int unsigned = b & 0xFF;             // recovers 200
int unsigned2 = Byte.toUnsignedInt(b); // same, more readable

Random numbers in a range. Math.random() returns a double; the cast truncates to an integer index:

int dice = (int) (Math.random() * 6) + 1;   // 1 to 6

The parentheses around Math.random() * 6 are essential. Without them, (int) Math.random() is always 0, and the result is always 1.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Java Type Casting and get answers drawn from it.

Signed-in readers only.