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
toStringmethods: 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
toStringmethod (inherited fromObjectclass). It's good practice to overridetoStringin your classes to provide a meaningful string representation. String.valueOf(Object obj): This method internally callsobj.toStringifobjis not null. Ifobjis 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
instanceoffor Downcasting: Always check withinstanceofbefore 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
toStringon an object reference, check fornullto 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 thetoStringmethod. - Understand Autoboxing: Be aware of where autoboxing and unboxing
occur, especially in performance-sensitive code or when dealing with
nullwrapper 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.