ZetCode

Java Type Conversions

Last modified: May 7, 2025

Type conversion (type casting) in Java means changing a value from one data type to another. This is needed when you want to use data in a different format, assign it to a variable of another type, or pass it to a method expecting a different type. Java supports several conversion methods for both primitive and object types.

Understanding type conversions helps you avoid runtime errors and data loss in Java programs.

Primitive Type Conversions

Java's primitive types (byte, short, int, long, float, double, char, boolean) can be converted to each other. There are two main types: implicit (widening) and explicit (narrowing).

Implicit Conversion (Widening or Automatic Type Conversion)

Widening conversion happens when a smaller type is converted to a larger type. Java does this automatically because it is safe and there is no data loss.

The typical widening conversion order is:

byte -> short -> int -> long -> float -> double

Also, char can be widened to int, long, float, or double.

WideningConversionDemo.java
package com.zetcode;

public class WideningConversionDemo {

    public static void main(String[] args) {

        int myInt = 100;
        long myLong = myInt;      // int to long (implicit)
        float myFloat = myLong;   // long to float (implicit)
        double myDouble = myFloat; // float to double (implicit)

        System.out.println("Original int: " + myInt);
        System.out.println("Widened to long: " + myLong);
        System.out.println("Widened to float: " + myFloat);
        System.out.println("Widened to double: " + myDouble);

        char myChar = 'A';
        int charToInt = myChar;   // char to int (implicit)
        System.out.println("Original char: " + myChar);
        System.out.println("Widened char to int: " + charToInt); // Prints 65
    }
}

In this example, values are promoted to a larger type automatically, without explicit casting.

Explicit Conversion (Narrowing or Manual Type Conversion)

Narrowing conversion happens when a larger type is converted to a smaller type. This must be done explicitly with a cast operator (targetType) because it can cause data or precision loss.

The typical narrowing conversion order is the reverse of widening:

double -> float -> long -> int -> short -> char -> byte
NarrowingConversionDemo.java
package com.zetcode;

public class NarrowingConversionDemo {

    public static void main(String[] args) {

        double myDouble = 123.456;
        float myFloat = (float) myDouble;   // double to float (explicit)
        long myLong = (long) myFloat;     // float to long (explicit, fractional part lost)
        int myInt = (int) myLong;         // long to int (explicit)
        short myShort = (short) myInt;    // int to short (explicit, potential overflow)
        byte myByte = (byte) myShort;     // short to byte (explicit, potential overflow)
        char myChar = (char) myInt;       // int to char (explicit, takes lower 16 bits)


        System.out.println("Original double: " + myDouble);
        System.out.println("Narrowed to float: " + myFloat);
        System.out.println("Narrowed to long: " + myLong);
        System.out.println("Narrowed to int: " + myInt);
        System.out.println("Narrowed to short: " + myShort);
        System.out.println("Narrowed to byte: " + myByte);
        System.out.println("Narrowed int to char: " + myChar);

        // Example of data loss due to overflow
        int largeInt = 300;
        byte smallByte = (byte) largeInt; // 300 is 100101100 in binary. Byte takes last 8 bits.
                                          // 300 % 256 = 44.
        System.out.println("Original large int: " + largeInt);
        System.out.println("Narrowed large int to byte: " + smallByte); // Output: 44

        double preciseDouble = 99.99;
        int truncatedInt = (int) preciseDouble;
        System.out.println("Original precise double: " + preciseDouble);
        System.out.println("Narrowed to int (truncation): " + truncatedInt); // Output: 99
    }
}

When narrowing, higher-order bits may be lost if the value is out of range for the target type. For floating-point to integer, the fractional part is simply removed.

Type Promotion in Expressions

Java promotes smaller types to larger ones in expressions to prevent data loss during calculations. The rules are:

ExpressionPromotionDemo.java
package com.zetcode;

public class ExpressionPromotionDemo {

    public static void main(String[] args) {

        byte b1 = 10;
        byte b2 = 20;
        // byte resultByte = b1 + b2; // Error: b1 + b2 results in an int
        int resultInt = b1 + b2;    // Correct: byte + byte -> int
        byte resultByteCasted = (byte) (b1 + b2); // Explicit cast needed for byte result

        System.out.println("b1 + b2 (as int): " + resultInt);
        System.out.println("b1 + b2 (casted to byte): " + resultByteCasted);

        short s = 5;
        float f = 10.5f;
        // short shortResult = s + f; // Error: s + f results in a float
        float floatResult = s + f;  // Correct: short + float -> float
        System.out.println("s + f (as float): " + floatResult);

        int i = 100;
        double d = 20.75;
        // int intResultSum = i + d; // Error: i + d results in double
        double doubleResult = i + d; // Correct: int + double -> double
        System.out.println("i + d (as double): " + doubleResult);
    }
}

Object Type Conversions (Casting)

For object types (classes and interfaces), casting lets you treat an object as another type within its inheritance tree.

Upcasting (Implicit)

Upcasting means treating a subclass object as its superclass. This is always safe and automatic because a subclass is a kind of its superclass.

ObjectCastingDemo.java
package com.zetcode.casting;

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

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

    void fetch() {
        System.out.println("Dog is fetching.");
    }
}

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

    void scratch() {
        System.out.println("Cat is scratching.");
    }
}

public class ObjectCastingDemo {

    public static void main(String[] args) {

        // Upcasting
        Dog myDog = new Dog();
        Animal myAnimalFromDog = myDog; // Implicit upcasting (Dog to Animal)

        Cat myCat = new Cat();
        Animal myAnimalFromCat = myCat; // Implicit upcasting (Cat to Animal)

        System.out.print("myAnimalFromDog (originally Dog) says: ");
        myAnimalFromDog.makeSound(); // Calls Dog's overridden makeSound()

        System.out.print("myAnimalFromCat (originally Cat) says: ");
        myAnimalFromCat.makeSound(); // Calls Cat's overridden makeSound()

        // myAnimalFromDog.fetch(); // Compile error: Animal type doesn't have fetch() method
                                 // The reference type determines accessible methods.

        // Using an array of superclass type
        Animal[] animals = new Animal[2];
        animals[0] = new Dog(); // Upcasting
        animals[1] = new Cat(); // Upcasting

        for (Animal animal : animals) {
            animal.makeSound(); // Polymorphism in action
        }
    }
}

When upcasting, you can only use methods and fields from the superclass (or those overridden by the subclass). Subclass-specific methods are not accessible through the superclass reference.

Downcasting (Explicit)

Downcasting means treating a superclass object as a subclass. This is risky and must be explicit, usually after checking with instanceof to avoid ClassCastException.

ObjectDowncastingDemo.java
package com.zetcode.casting; // Assuming Animal, Dog, Cat are in the same package or imported

public class ObjectDowncastingDemo {

    public static void main(String[] args) {
        Animal myAnimal = new Dog(); // Upcasted to Animal, actual object is Dog
        // Animal anotherAnimal = new Animal(); // Actual object is Animal

        // Attempting to downcast myAnimal to Dog
        if (myAnimal instanceof Dog) {
            Dog myDog = (Dog) myAnimal; // Explicit downcasting
            System.out.print("Downcasted Dog object says: ");
            myDog.makeSound(); // Calls Dog's makeSound()
            myDog.fetch();     // Now fetch() is accessible
        } else {
            System.out.println("Cannot downcast myAnimal to Dog.");
        }

        Animal generalAnimal = new Animal();
         // Attempting to downcast generalAnimal to Dog (will fail if not checked)
        if (generalAnimal instanceof Dog) {
            Dog specificDog = (Dog) generalAnimal; // This line won't be reached
            specificDog.fetch();
        } else {
            System.out.println("generalAnimal is not an instance of Dog. Cannot downcast.");
        }

        // Example leading to ClassCastException
        Animal possiblyCat = new Cat();
        // Dog notADog = (Dog) possiblyCat; // This would throw ClassCastException if executed
                                        // because a Cat is not a Dog.

        try {
            Animal anAnimal = new Cat(); // Actual object is Cat
            Dog aDog = (Dog) anAnimal;   // Attempting to cast Cat to Dog
            aDog.fetch();
        } catch (ClassCastException e) {
            System.err.println("Error during downcasting: " + e.getMessage());
        }
    }
}

The instanceof operator checks if an object is a certain type or a subclass of it. Always use instanceof before downcasting to avoid errors.

Conversions Involving Strings

Strings are objects in Java. Converting between strings and primitives or other objects is common.

Primitive to String

String to Primitive

This is usually done with parseXxx methods of wrapper classes (e.g., Integer.parseInt(String)). If the string is not valid, a NumberFormatException is thrown.

Object to String

StringConversionDemo.java
package com.zetcode;

import java.util.Date;

public class StringConversionDemo {

    public static void main(String[] args) {

        // Primitive to String
        int num = 123;
        String strNum1 = String.valueOf(num);
        String strNum2 = Integer.toString(num);
        String strNum3 = "" + num;
        System.out.println("Primitive to String: " + strNum1 + ", " + strNum2 + ", " + strNum3);

        double val = 45.67;
        String strVal = String.valueOf(val);
        System.out.println("Double to String: " + strVal);

        // String to Primitive
        String strInt = "100";
        String strDouble = "200.50";
        String strInvalid = "abc";

        try {
            int parsedInt = Integer.parseInt(strInt);
            double parsedDouble = Double.parseDouble(strDouble);
            System.out.println("String to int: " + parsedInt);
            System.out.println("String to double: " + parsedDouble);

            // int invalidParse = Integer.parseInt(strInvalid); // This would throw NumberFormatException
        } catch (NumberFormatException e) {
            System.err.println("Error parsing string: " + e.getMessage());
        }

        // Boolean parsing
        String trueStr = "true";
        String falseStr = "FalSe"; // Case-insensitive for "true"
        boolean bTrue = Boolean.parseBoolean(trueStr); // true
        boolean bFalse = Boolean.parseBoolean(falseStr); // false (only "true" ignoring case is true)
        System.out.println("String 'true' to boolean: " + bTrue);
        System.out.println("String 'FalSe' to boolean: " + bFalse);


        // Object to String
        Date today = new Date();
        String dateStr = today.toString(); // Uses Date's overridden toString()
        System.out.println("Date object to String: " + dateStr);

        Object nullObj = null;
        String nullStr = String.valueOf(nullObj); // Safely handles null, returns "null"
        System.out.println("String.valueOf(nullObj): " + nullStr);
        // String problematicNullStr = nullObj.toString(); // This would throw NullPointerException!
    }
}

Autoboxing and Unboxing

Autoboxing is when Java automatically converts primitives to their wrapper classes (e.g., int to Integer). Unboxing is the reverse. This makes code simpler and more readable.

This feature lets you write Integer myIntegerObject = 100; instead of Integer myIntegerObject = new Integer(100); (the constructor is deprecated; use valueOf instead).

AutoboxingUnboxingDemo.java
package com.zetcode;

import java.util.ArrayList;
import java.util.List;

public class AutoboxingUnboxingDemo {

    public static void main(String[] args) {

        // Autoboxing: primitive int to Integer object
        Integer integerObject = 100; // Compiler converts to Integer.valueOf(100)
        System.out.println("Autoboxed Integer object: " + integerObject);

        // Unboxing: Integer object to primitive int
        int primitiveInt = integerObject; // Compiler converts to integerObject.intValue()
        System.out.println("Unboxed primitive int: " + primitiveInt);

        // Example in collections
        List<Integer> numberList = new ArrayList<>();
        numberList.add(10); // Autoboxing: 10 (int) becomes Integer.valueOf(10)
        numberList.add(20);

        int firstElement = numberList.get(0); // Unboxing: Integer object from list to int
        System.out.println("First element from list (unboxed): " + firstElement);

        // Pitfall: NullPointerException during unboxing
        Integer nullInteger = null;
        try {
            // int problematicInt = nullInteger; // This would throw NullPointerException
                                               // as it tries to call nullInteger.intValue()
            if (nullInteger != null) { // Always check for null before unboxing
                 int safeInt = nullInteger;
                 System.out.println("Safely unboxed: " + safeInt);
            } else {
                System.out.println("Cannot unbox a null Integer object.");
            }
        } catch (NullPointerException e) {
            System.err.println("Error during unboxing: " + e.getMessage());
        }
    }
}

While convenient, autoboxing/unboxing can create unnecessary objects in performance-critical code. Unboxing a null wrapper object causes a NullPointerException.

Summary of Key Conversions & Potential Issues

Conversion Type Description Mechanism Potential Issues
Primitive Widening Smaller to larger numeric type (e.g., int to long) Implicit (Automatic) None (Safe)
Primitive Narrowing Larger to smaller numeric type (e.g., double to int) Explicit Cast (type)value Data loss, precision loss, overflow
Object Upcasting Subclass to Superclass/Interface Implicit (Automatic) None (Safe, but loses access to subclass-specific members via superclass reference)
Object Downcasting Superclass/Interface to Subclass Explicit Cast (SubclassType)object ClassCastException if object is not an instance of target type
Primitive to String e.g., int to String String.valueOf, Wrapper.toString, "" + primitive Generally safe
String to Primitive e.g., String to int Wrapper.parseXxx NumberFormatException if string format is invalid
Object to String Any object to String obj.toString, String.valueOf(obj) NullPointerException if obj.toString is called on null (String.valueOf handles null safely)
Autoboxing Primitive to Wrapper (e.g., int to Integer) Implicit (Automatic) Performance overhead in loops if not careful
Unboxing Wrapper to Primitive (e.g., Integer to int) Implicit (Automatic) NullPointerException if wrapper object is null

Best Practices for Type Conversion

Source

Official Java Primitive Data Types and Conversions

Java's type conversion system is flexible, allowing data to move between primitive and object types. Implicit conversions are seamless, but explicit conversions require care to avoid data loss or runtime errors. Knowing the rules and pitfalls helps you write safer, more reliable Java code.

Author

My name is Jan Bodnar. I am a programmer with many years of experience. Since 2007, I have written over 1,400 articles and eight e-books. With more than eight years of teaching, I enjoy sharing knowledge and helping others learn programming.

List all Java tutorials.