Primitive Type Conversions

Type conversions between primitive types in Java can be broadly categorized as follows:

  1. Widening conversions
  2. Implicit narrowing conversions
  3. Narrowing conversions requiring explicit cast
  4. Numeric promotion

Only numeric types can participate in type conversions. These types include the integer types char, byte, short, int and long and the floating-point types float and double. boolean is not type-compatible with any other primitive data type i.e. boolean values can’t be converted to other primitive types and vice-versa.

The table that follows presents a summary of the sizes and bit representations of these types, that will go a long way in understanding the conversions better.

Type Bit representation
char  16 bits; Unsigned Unicode UTF-16
byte  8 bits; Signed 2’s complement
short  16 bits; Signed 2’s complement
int  32 bits; Signed 2’s complement
long  64 bits; Signed 2’s complement
float  32 bits; IEEE 754-1985
double  64 bits; IEEE 754-1985

Widening and Narrowing

A type conversion is termed widening when it occurs from a narrower data type to a broader data type. Narrower and broader here mean that the set of valid values of the narrower type is a subset of that of the broader type. The picure below shows the widening conversions. The direction of the arrows represent the direction of widening.

Figure 1: Widening primitive conversions
Figure 1: Widening primitive conversions

All other conversions are termed narrowing as they convert a wider type to a narrower type. Note that conversions between char and byte/short are narrowing. Byte is a smaller type (8 bits). Short is same size as a char (both 16 bits), but short is signed whereas char is unsigned. So a short can’t cover the entire range of valid char values.

Let’s now look at the conversions and the contexts in which they can occur in more detail.

1. Widening conversions

Widening conversions are done implicitly and don’t require a cast. A cast, though redundant, can still be used. Lets look at the following assignments.

int a = 5;
long b = a; //(1)
long c = (long) a; //(2)

At (1), int is implicitly converted to a long. At (2), int is converted to a long using a redundant but valid cast.

Resulting value:

Since the destination type can hold all values in the range of the source type, destination type being broader, there is no loss of magnitude. However there can be a loss of precision when the source type is an integer type and the destination type is a floating-point type. Although the range of magnitudes of floating-point types is larger than any integer types, floating-point types store only a limited precision which may result in loss of precision of the least significant digits. Lets consider the following example.

long x = 12345676899L;
float y = x; // (1)
System.out.println("y = " + y);

y = 1.23456768E10
The widening conversion at (1) results in a loss of precision as is evident from the output.


A widening conversion can occur in the context of assignment as well as parameter passing during method invocation. Numeric Promotion, discussed in section 4 below, is actually a special form of widening conversion that occurs under some specific conditions as we’ll see.


A widening conversion between integer types is done by extending the sign (leftmost) bit to take up newly added higher order bits. This preserves the original value as per 2’s complement notation. A widening conversion from a char to an integer type is done by setting higher order bits to zero. Conversion from integral to floating-point values involves conversion from 2’s complement to IEEE 754-1985 notation.

2. Implicit narrowing conversions

Narrowing conversions can occur implicitly when all of the following conditions are satisfied:

  1. The source value is a constant expression i.e. an expression involving only literals or compile-time constants
  2. The source type is one of byte, char, short or int
  3. The destination type is one of byte, char or short
  4. The source value is within the valid range of values for the destination type

For example, we can assign an int value to a byte or short variable without needing  a cast if the source expression is constant and the value of the expression is within the valid range for the destination variable type.

int x = 12;
final int y = 12;
byte a = 12; // (1) OK
byte b = y; // (2) OK
// byte c = 130; // (3) Not OK
// byte d = x; // (4) Not OK

In the code above, the assignment at (1) results in an implicit conversion from int literal 12 to byte since 12 is a constant expression and is within the range of byte type. Assignment at (2) also works since y is a constant expression. Assignment at (3), does not compile since the source value is outside the valid range for byte type. The assignment at (4) doesn’t compile since the source is not a constant expression. The assignments at (3) and (4) require an explicit cast to compile as we’ll see in next section.


An implicit narrowing conversion can occur only during assignments and not during method invocation. The reason for this restriction is to simplify the process of binding a method invocation to a method definition in case of method overloading.


A narrowing conversion is done by discarding the higher order extra bits in the 2’s complement bit representation of the source value. For example, in a conversion from int to short, the higher order (i.e. leftmost) 16 bits are discarded and the remaining 16 bits constitute the new short value. Since for an implicit narrowing conversion, the source value is within the range of the destination type, this means that the discarded bits are all 0s (in case of a positive value) or all 1s (in case of a negative value). Thus discarding the extra higher order bits affects neither the magnitude nor the sign when destination type is short or byte. When destination type is char, the resulting bit pattern is interpreted as a Unicode-16 char value.

Resulting value:

Since a pre-requisite for an implicit narrowing conversion is that the source value be within the valid range of the destination type, such conversions don’t result in any loss of information.

3. Narrowing conversions requiring explicit cast

Any narrowing conversion that does not satisfy the conditions for an implicit conversion require an explicit cast. For example, the assignments at (3) and (4) in the code above can be rewritten with an explicit cast to remove the compilation errors:

byte c = (byte) 130; // (5) OK
byte d = (byte) x; // OK


An explicit narrowing conversion can occur during assignments and parameter passing.


The narrowing between integer types is done as described in the previous section. Narrowing from floating-point to integer types involves conversion from IEEE 754-1985 to 2’s complement notation.

Resulting value:

When source value is larger than the range of the destination type, the discarded bits actually contributed towards the magnitude in the original value and hence there is a loss of magnitude. Also, the highest order (leftmost) bit in the resulting value is interpreted as the sign bit as per 2’s complement notation. This may be different from the highest order bit in the original value and hence the sign of the value may change. For example, if we print the value of the variable c declared at (5) above:

System.out.println("c = " + c);

the output is
c = -126
At (5), c is assigned int literal 130 which is 00000000000000000000000010000010. Discarding the 24 high order bits results in 10000010  which is -126 when interpreted as per 2’s complement notation. Both the magnitude and sign got changed.

Both char and short types are 16 bits each however char is unsigned whereas short is a signed type. Conversions between them can also result in loss of information.

Floating-point values get rounded down towards zero when converted to integer types. For example,

float x = 12.5F;
int y = (int) x;
System.out.println("y = " + y);

y = 12

When the floating-point value is outside the range of the integer type, the result is Integer.MIN_VALUE or Integer.MAX_VALUE.

Conversion from double to float type can also result in loss of magnitude as well as precision. Specifically, double values that are too large for the float type result in +Infinity or -Infinity. double values that are too small result in +0.0 or -0.0.

4. Numeric promotion

Numeric promotion is a widening conversion that applies implicitly to the operands of numeric operators under specific contexts. There are two types of numeric promotions:

  1. Unary numeric promotion applies to the operand of a unary operator or to the individual operands of a binary operator. The rule states that if the type of the operand is narrower than int, then it is converted to int, otherwise it is not changed. Unary numeric promotion is applied to the following:
    • Operand of unary + and – operators and unary negation operator ~
    • Each individual operand of the binary shift operators <<, >> and >>>
    • Size expression in array creation using “new arr[size]” and index expression in array element access using “arr[index]
  2. Binary numeric promotion applies to the operands of a binary numeric operator taking both the operands into account together. The rule states that if T is the broader type of the types of the two operands, then both the operands are converted to int if T is narrower than int, otherwise both are converted to T. Binary numeric promotion is applied to the following:
    • Operands of arithmetic operators +, -, *, /, %
    • Operands of relational operators <, <=, >, >= and equality operators == and !=
    • Operands of integer bitwise operators &, ^ and |

Numeric promotion is applied during the evaluation of an expression. After the evaluation, implicit or explicit type conversions may be required for the assignment of the evaluated value to a variable. For example,

short a = 10;
short b = (short) (a + 10); // (1)

At (1) above, binary numeric promotion occurs during evaluation of a + 10, a‘s value is widened to int and int 10 is added. The result is an int value of 20 which requires an explicit short cast to be assigned to a short variable b. Let’s look at another example:

float a = 12.5;
int b = 5;
byte c = 10;
float d = a * ( b + c ) // (1)

Evaluation of a * ( b + c ) at (1) above proceeds as follows (in terms of types):

float * ( int + byte )
=> float * ( int + ( byte => int ) )
=> float * ( int + int )
=> float * int
=> float * ( int => float )
=> float * float
=> float

Compound assignment operators (+=, -=, *=, /=, %=) and unary increment/decrement operators (++, –) apply a cast implicitly. For example, for an initialized byte variable a

a = (byte) (a + 10); // requires explicit cast
a += 10; // doesn't require a cast; cast is implicit
a = (byte) (a + 1); // requires explicit cast
a++; // doesn't require a cast; cast is implicit


A numeric promotion occurs during the evaluation of an expression.


A numeric promotion is a widening conversion and uses the same mechanism as a normal widening conversion.

Effect on resulting value:

There is no loss of information during a numeric promotion.

Other types of conversions involving primitive types

  1. Boxing/unboxing: Boxing and unboxing refers to implicit conversions from primitive to the corresponding wrapper types and vice-versa. Such conversions occur automatically in the context of both assignment and parameter passing.
  2. String concatenation: + operator is an overloaded operator that works with String operands to effect concatenation. When one of the operands of the + is a String, the operator works as concatenation operator resulting in another String. If one of the operands is not a String, it automatically gets converted to a String before applying the concatenation. In case of primitive types, the converted String is a string representation of the operand’s value. In case of references, the String is obtained by calling the toString() method on the operand.