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
.
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
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:
- If one operand is
double
, the other is converted todouble
. - Else, if one operand is
float
, the other is converted tofloat
. - Else, if one operand is
long
, the other is converted tolong
. - Otherwise (
byte
,short
,char
,int
), both operands are converted toint
.
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.
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
.
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
- Using
String.valueOf
: This method is overloaded for all primitive types (e.g.,String.valueOf(int)
,String.valueOf(double)
). - Using wrapper class
toString
methods: E.g.,Integer.toString(int)
,Double.toString(double)
. - Concatenation with an empty string: E.g.,
"" + myInt
. While simple, this can be less efficient due to string concatenation overhead.
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
- Every object has a
toString
method (inherited fromObject
class). It's good practice to overridetoString
in your classes to provide a meaningful string representation. String.valueOf(Object obj)
: This method internally callsobj.toString
ifobj
is not null. Ifobj
is null, it returns the string "null" (avoiding aNullPointerException
).
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).
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
- Prefer Widening: Use widening conversions whenever possible as they are safe and automatic.
- Be Cautious with Narrowing: Understand the potential for data loss or overflow. Cast explicitly and ensure the value is within the target type's range if critical.
- Use
instanceof
for Downcasting: Always check withinstanceof
before downcasting objects to preventClassCastException
. - Handle Exceptions: When parsing strings to numbers (
parseXxx
), be prepared to catchNumberFormatException
. - Null Checks: Before unboxing wrapper objects or calling methods like
toString
on an object reference, check fornull
to avoidNullPointerException
.String.valueOf(Object)
is safer for converting objects to strings when nulls are possible. - Override
toString
: Provide meaningful string representations for your custom classes by overriding thetoString
method. - Understand Autoboxing: Be aware of where autoboxing and unboxing
occur, especially in performance-sensitive code or when dealing with
null
wrapper objects.
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
List all Java tutorials.