Sitelet https://howtodoinjava.com/java/basics/primitive-data-types-in-java/

Java Primitive Data Types: Sizes, Ranges and Defaults

Java has eight primitive data types that hold plain values, not object references. Sizes, ranges, defaults, literals, overflow, floating-point precision, casting rules and wrapper classes, with tested Java 25 examples.

Primitive data types in java

Java has eight primitive data types, namely byte, short, int, long, float, double, char and boolean, and a variable of a primitive type holds the value itself, not a reference to an object. Each type has a fixed size and range that the Java Language Specification defines, so an int is 32 bits on every JVM and every operating system.

We use primitive types for counters, prices, flags, loop indexes and every other plain number, character or yes/no value in a program. They take less memory than objects and can never be null.

The following example declares one variable of each primitive type and shows three results that surprise many developers, with the result of each line as a comment.

byte level = 127;                     // byte max 127
short year = 2026;                    // short max 32767
int stock = 2_147_483_647;            // Integer.MAX_VALUE
long views = 9_000_000_000L;          // needs the L suffix
float rating = 4.5f;                  // needs the f suffix
double price = 19.99;                 // a decimal literal is a double
char grade = 'A';                     // one UTF-16 character, code 65
boolean inStock = true;               // true or false only

int wrapped = Integer.MAX_VALUE + 1;  // -2147483648 (overflow, no error)
double sum = 0.1 + 0.2;               // 0.30000000000000004
byte narrowed = (byte) 200;           // -56

Notice the last three lines. An int that goes past its maximum wraps around to the minimum without an error. A double cannot store the exact value 0.1, and a cast to a smaller type keeps only the lowest bits of the value.

In the next sections, we cover the size, range and literals of each type, the default values, the conversions between types and the wrapper classes. All examples run on Java 25.

1. What Is a Primitive Type in Java?

Java splits all data types into two groups. A primitive type is built into the language and named by a reserved keyword such as int. A reference type is a class, an interface, an array or an enum, and a variable of a reference type holds a reference (the address of an object), not the object.

The difference shows up when we copy a variable. Copying a primitive copies the value, so the two variables are independent. Copying a reference gives us two variables that point to the same object, so a change through one variable is visible through the other.

int stock = 5;
int copy = stock;
copy = 10;                            // stock = 5

int[] stocks = {5};
int[] sameArray = stocks;
sameArray[0] = 10;                    // stocks[0] = 10

The eight primitive types fall into two groups. The boolean type stands alone, and the other seven are numeric types. The numeric types are the integral types (byte, short, int, long and char) and the floating-point types (float and double).

Tree of the eight Java primitive types. Primitive types split into boolean and numeric types. Numeric types split into integral types (byte, short, int, long, char) and floating-point types (float, double). Each box shows the size in bits.
char is an integral type in Java, so we can do arithmetic on it, while boolean is not a number at all.

The table lists the size, the default value of a field and the range of each type. A primitive type also has a wrapper class, which we need when the value goes into a collection, as we will see in section 8.

TypeSizeDefault (fields)RangeWrapper class
byte8 bits0-128 to 127Byte
short16 bits0-32768 to 32767Short
int32 bits0-2147483648 to 2147483647Integer
long64 bits0L-9223372036854775808 to 9223372036854775807Long
float32 bits0.0fabout 1.4E-45 to 3.4E38, positive or negativeFloat
double64 bits0.0dabout 4.9E-324 to 1.8E308, positive or negativeDouble
char16 bits‘\u0000’‘\u0000’ to ‘\uffff’ (0 to 65535)Character
booleannot specifiedfalsetrue or falseBoolean

The default value applies only to fields and array elements. Local variables have no default, as section 6 shows.

2. Integer Types: byte, short, int and long

The four integer types store whole numbers in two’s complement form (the standard binary form for signed numbers), so each type covers as many negative values as positive ones, plus zero. The types differ only in size, and each type has twice the bits of the type before it, so long covers far more values than int.

We use int for whole numbers by default, and long when the value can pass about 2.1 billion, such as file sizes in bytes or timestamps in milliseconds. The types byte and short save memory only in large arrays, for example a byte[] that holds the content of an uploaded file.

2.1. Integer Literals

A whole number in the source code, such as 255, is an int literal. We add the suffix L to write a long literal, and the compiler rejects an int literal outside the int range with the error “integer number too large”. We always use an uppercase L, because a lowercase l looks like the digit 1.

We can write a literal in four number systems and use underscores between digits to make long numbers readable.

int decimal = 255;                    // 255
int hex = 0xFF;                       // 255
int octal = 0377;                     // 255
int binary = 0b1111_1111;             // 255
long population = 8_100_000_000L;     // 8100000000
byte small = 100;                     // fits, -128 to 127
short medium = 30_000;                // fits, -32768 to 32767

There are no byte or short literals. The compiler accepts an int literal for a byte or short variable when the value fits in the range, and reports an error when it does not.

byte level = 150;
error: incompatible types: possible lossy conversion from int to byte

The wrapper classes hold the limits and sizes as constants, so we never need to type the numbers ourselves.

int intMin = Integer.MIN_VALUE;       // -2147483648
int intMax = Integer.MAX_VALUE;       // 2147483647
long longMax = Long.MAX_VALUE;        // 9223372036854775807
int intBits = Integer.SIZE;           // 32
int longBytes = Long.BYTES;           // 8

2.2. Integer Overflow

When the result of an integer operation does not fit in the type, Java keeps the lowest bits of the result and throws no exception. So Integer.MAX_VALUE + 1 becomes Integer.MIN_VALUE, and the program goes on with a wrong number.

Say a subscription app computes a 30-day trial period in milliseconds. All the numbers in the expression are int literals, so Java multiplies them as int values, and the result overflows before it is stored in the long variable. Writing the first number as 30L makes the whole calculation a long calculation.

int wrong = 30 * 24 * 60 * 60 * 1000;    // -1702967296 (overflow)
long right = 30L * 24 * 60 * 60 * 1000;  // 2592000000

When a wrong value must never pass unnoticed, we use the exact arithmetic methods of the Math class, such as Math.addExact() and Math.multiplyExact(). They throw an ArithmeticException on overflow instead of returning a wrong value.

try {
  int checked = Math.multiplyExact(30 * 24 * 60 * 60, 1000);
  System.out.println(checked);
} catch (ArithmeticException e) {
  System.out.println("Too large for int: " + e.getMessage());
}
// Too large for int: integer overflow

Java has no unsigned integer types (except char). For the rare cases that need one, such as a checksum from another system, the classes Integer and Long have methods such as Integer.toUnsignedString() and Integer.divideUnsigned().

3. Floating-Point Types: float and double

The types float and double store numbers with a fractional part, such as 19.99, in the IEEE 754 binary format. The format stores the significant digits and the position of the decimal point separately, so the point can “float”. A float uses 32 bits and keeps about 6 to 7 significant decimal digits, whereas a double uses 64 bits and keeps about 15 to 16 digits.

Layout of a 32-bit IEEE 754 float. Bit 31 is the sign, bits 30 to 23 are the 8-bit exponent and bits 22 to 0 are the 23-bit fraction.
A float spends 1 bit on the sign, 8 bits on the exponent and 23 bits on the fraction, which limits it to about 7 significant digits.

A decimal literal such as 19.99 is a double. For a float literal, we add the suffix f, and without it the compiler reports “possible lossy conversion from double to float”.

float rating = 4.5f;                  // 4.5
double price = 19.99;                 // 19.99
double distance = 1.5e3;              // 1500.0
float third = 1.0f / 3;               // 0.33333334
double thirdD = 1.0 / 3;              // 0.3333333333333333

We use double for decimal numbers by default. The extra precision costs only 4 more bytes per value, and the Math methods take and return double. A float makes sense in large arrays, for example the vertex data of a 3D model.

3.1. Precision Errors

The binary format cannot store most decimal fractions without a small error, in the same way that 1/3 has no exact decimal form. The value 0.1 is stored as the nearest binary fraction, so small errors show up in sums and comparisons.

double sum = 0.1 + 0.2;                     // 0.30000000000000004
boolean equal = sum == 0.3;                 // false
boolean close = Math.abs(sum - 0.3) < 1e-9; // true

So we never compare double values with == after a calculation. To compare float and double values, we check that the difference is smaller than a tolerance, as the last line shows.

Money is the case where these errors hurt most. A shop that adds 0.10 and 0.20 to an invoice must print 0.30, not 0.30000000000000004, so we store amounts as BigDecimal and create them from strings.

BigDecimal total = new BigDecimal("0.10").add(new BigDecimal("0.20"));  // 0.30

3.2. Infinity, NaN and Division by Zero

A floating-point division by zero does not throw an exception. The result is positive or negative infinity, and 0.0 / 0 gives NaN (Not a Number). The value NaN is not equal to anything, not even to itself, so we test it with Double.isNaN().

double positive = 1.0 / 0;            // Infinity
double negative = -1.0 / 0;           // -Infinity
double notANumber = 0.0 / 0;          // NaN
boolean nanEquals = notANumber == notANumber;   // false
boolean isNan = Double.isNaN(notANumber);       // true

An integer division by zero behaves differently and throws an ArithmeticException.

try {
  int perItem = 10 / 0;
  System.out.println(perItem);
} catch (ArithmeticException e) {
  System.out.println(e.getMessage());   // / by zero
}

Notice the meaning of MIN_VALUE for the floating-point types. Float.MIN_VALUE and Double.MIN_VALUE are the smallest positive values, not the most negative ones. The most negative double is -Double.MAX_VALUE.

float floatMin = Float.MIN_VALUE;     // 1.4E-45 (smallest positive value)
float floatMax = Float.MAX_VALUE;     // 3.4028235E38
double doubleMin = Double.MIN_VALUE;  // 4.9E-324
double doubleMax = Double.MAX_VALUE;  // 1.7976931348623157E308

4. The char Type

A char is a 16-bit unsigned integer that holds one UTF-16 code unit, which is one character of the Unicode set from ‘\u0000’ to ‘\uffff’. Since char is an integral type, every character also has a numeric code, such as 65 for ‘A’.

We write a char literal in single quotes. A Unicode escape ‘\uXXXX’ takes four hex digits, and the compiler accepts a plain number in the char range as well.

char letter = 'A';                    // A
char fromUnicode = 'A';          // A
char fromCode = 66;                   // B
int code = 'A';                       // 65
char next = (char) ('A' + 1);         // B
int plusOne = 'A' + 1;                // 66
char tab = '\t';                      // tab character

Notice the cast in (char) (‘A’ + 1). Java turns both operands into int before the addition, so the result is an int, and we need the cast to store it in a char.

The escape sequences cover the characters that we cannot type inside quotes. Java has eight of them, plus the Unicode escape.

EscapeCharacter
\bbackspace
\ttab
\nline feed (new line)
\fform feed
\rcarriage return
\”double quote
\’single quote
\\backslash

Java 15 added \s (a space) and a line-ending escape for text blocks. Java also accepts octal escapes such as ‘\101’ (the letter A), but the Unicode escape is easier to read.

4.1. Characters Outside the 16-Bit Range

Unicode has more than 65,536 characters, so one char cannot hold every character. An emoji such as the smiling face needs two char values (a surrogate pair). A chat app that limits messages to 100 characters with length() counts each emoji twice, so we count code points instead.

String smiley = "\uD83D\uDE00";                               // one emoji
int length = smiley.length();                                 // 2 (two char values)
int codePoints = smiley.codePointCount(0, smiley.length());   // 1
int maxChar = Character.MAX_VALUE;                            // 65535

5. The boolean Type

The boolean type has two values, true and false. Conditions in if statements and loops must be boolean, so Java, unlike C, never treats 0 or 1 as a truth value.

We cannot cast a boolean to a number or a number to a boolean. The compiler rejects the cast (int) done with the error “boolean cannot be converted to int”, so we use the conditional operator when we need a number.

int stock = 3;
boolean inStock = stock > 0;               // true
boolean canShip = inStock && stock >= 5;   // false
int flag = inStock ? 1 : 0;                // 1

The JLS does not define the size of a boolean. The JVM specification says that the JVM works on boolean values as int values and stores a boolean[] array as a byte array with 8 bits per element.

6. Default Values of Primitive Types

Java gives every field and every array element a default value when the object or array is created. The default is zero for the numeric types, ‘\u0000’ for char and false for boolean. A reference field, such as a String, starts as null.

The following example creates a Product object without setting any field and reads the defaults.

class Product {
  int stock;
  long views;
  double price;
  float rating;
  char grade;
  boolean active;
  String name;
}

Product product = new Product();
int stock = product.stock;            // 0
long views = product.views;           // 0
double price = product.price;         // 0.0
float rating = product.rating;        // 0.0
int grade = product.grade;            // 0 ('\u0000')
boolean active = product.active;      // false
String name = product.name;           // null

boolean[] flags = new boolean[3];     // [false, false, false]
int[] counts = new int[3];            // [0, 0, 0]

Local variables get no default value. The compiler checks that a local variable is assigned before every read and stops with an error otherwise.

int score;
System.out.println(score);
error: variable score might not have been initialized

7. Converting Between Primitive Types

Java converts a value to a larger type without a cast, because no value is lost. This step is called a widening conversion. The opposite direction is a narrowing conversion, and it needs an explicit cast, because the value may not fit.

Widening conversion paths between Java primitive types. byte goes to short, short goes to int, char goes to int, int goes to long, long goes to float and float goes to double. Arrows from int to float, long to float and long to double are dashed red because they can lose precision, while int to double is solid. boolean has no conversion to or from numbers.
Every arrow is a widening conversion that needs no cast, but the dashed arrows to float and double can lose precision.

7.1. Widening Conversions

A widening conversion happens on assignment and when we pass a value to a method parameter of a larger type.

int stock = 120;
long bigStock = stock;                // 120 (widening)
double asDouble = stock;              // 120.0 (widening)
int fromChar = 'A';                   // 65 (widening)

Widening keeps the magnitude of the value, but not always every digit. A float has 24 bits for the digits, so an int above 16,777,216 can lose its last digits when Java widens it to float.

int exact = 16_777_217;
float lossy = exact;                  // 1.6777216E7 (lost the last digit)
int back = (int) lossy;               // 16777216

7.2. Narrowing Casts

A cast to a smaller integer type keeps only the lowest bits of the value. A cast from double to an integer type drops the fractional part (it rounds toward zero), and a value outside the range becomes the nearest limit.

byte fromInt = (byte) 200;            // -56
short fromLarge = (short) 70_000;     // 4464
int truncated = (int) 3.99;           // 3
int negative = (int) -3.99;           // -3
int clamped = (int) 1e10;             // 2147483647
int fromNan = (int) Double.NaN;       // 0
long rounded = Math.round(3.99);      // 4

When we want the nearest whole number, we call Math.round(), as the last line shows. To round to n decimal places, we use BigDecimal or String.format().

A cast never reports that the value did not fit. When the value comes from outside, such as a quantity from a form, we check the range before the cast and throw an exception.

static byte toByteExact(int value) {
  if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
    throw new ArithmeticException(value + " does not fit in a byte");
  }
  return (byte) value;
}

byte safe = toByteExact(300);         // ArithmeticException: 300 does not fit in a byte

For long to int, the JDK has Math.toIntExact(), which throws an ArithmeticException on overflow.

7.3. Numeric Promotion in Expressions

Java does arithmetic on int, long, float and double only. Before an operation, Java converts byte, short and char operands to int, so the sum of two byte values is an int.

byte a = 10;
byte b = 20;
int sum = a + b;                      // 30 (byte + byte is an int)
byte total = (byte) (a + b);          // 30
byte counter = 10;
counter += 300;                       // 54 (+= casts back to byte)

The compound operator += contains a hidden cast to the type of the left side. So counter += 300 compiles, while counter = counter + 300 does not, and the result 310 wraps around to 54.

8. Primitive Types and Wrapper Classes

Collections and generics work with objects only, so a List<int> does not compile. Each primitive type has a wrapper class, such as Integer for int, and Java converts between the two for us. The conversion from int to Integer is called autoboxing, and the conversion back is called unboxing.

List<Integer> stocks = new ArrayList<>();
stocks.add(5);                        // autoboxing int -> Integer
int first = stocks.get(0);            // 5 (unboxing)

A wrapper variable can be null, and unboxing a null throws a NullPointerException. For example, a shop reads the stock of an item from a Map, and the item is not in the map. The method get() returns null, so we use getOrDefault() when the result goes into a primitive variable.

Map<String, Integer> stockByItem = Map.of("apple", 5);
Integer missing = stockByItem.get("banana");               // null
int safe = stockByItem.getOrDefault("banana", 0);          // 0

int crash = stockByItem.get("banana");                     // NullPointerException

We also compare wrapper objects with equals(), never with ==. The Integer cache returns the same object for values from -128 to 127, so == works for small values and fails for larger ones.

Integer small1 = 127;
Integer small2 = 127;
Integer large1 = 128;
Integer large2 = 128;
boolean sameSmall = small1 == small2;       // true (cached object)
boolean sameLarge = large1 == large2;       // false (two objects)
boolean equalLarge = large1.equals(large2); // true

The wrapper classes also hold the parsing methods, such as Integer.parseInt(), which we use to convert a String to an int.

9. Primitive Data Types FAQs

9.1. Is String a Primitive Data Type in Java?

No. String is a class in the java.lang package, so a String variable holds a reference. Java gives String special support, such as literals in double quotes and the + operator, which makes it look like a primitive type.

9.2. How Much Memory Does a boolean Take?

The Java specifications do not fix the size. The JVM works on single boolean values as 32-bit int values, and HotSpot stores a boolean field in one byte and a boolean[] array with one byte per element. For a large set of flags, java.util.BitSet stores one bit per flag.

9.3. Why Is char 16 Bits in Java?

Java was designed in the early 1990s, when Unicode had fewer than 65,536 characters, so 16 bits were enough for every character. Unicode grew later, and since Java 5, characters above ‘\uffff’ are stored as two char values, as section 4.1 shows.

9.4. Can a Primitive Variable Be null?

No. A primitive variable always holds a value, and the compiler rejects int count = null;. When a value can be missing, such as an optional age in a form, we use the wrapper type Integer or an OptionalInt.

9.5. Which Type Should We Use for Big Numbers?

When a value does not fit in a long, we use java.math.BigInteger, which has no upper limit. For decimal values that must be exact, such as money, we use BigDecimal. Both classes are slower than primitive types, so we use them only where the range or the precision requires it.

10. Conclusion

Java has eight primitive types with a fixed size on every platform. The integer types byte, short, int and long store whole numbers, float and double store binary fractions, char stores one UTF-16 code unit, and boolean stores true or false.

Most bugs with primitive types come from their limits. Integer overflow and narrowing casts give wrong numbers without an error, whereas double values carry small binary errors into sums and comparisons. The Math methods such as Math.multiplyExact() throw an exception instead of returning a wrong number, and BigDecimal keeps money values exact.

Fields and array elements start with a default value, but local variables must be assigned before use. When a primitive value goes into a collection, Java boxes it into its wrapper class, so we watch for null values and compare wrappers with equals().

11. References

Happy Learning !!

Source Code on Github

Leave a Comment

  1. when we assisgn the negative numeric value to char datatype using explicit conversion its not showing any error.why?

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.