Skip to main content

Command Palette

Search for a command to run...

Java Data Types at a glance

Java Types briefly explained and a bit of Kotlin at the end

Updated
8 min read
Java Data Types at a glance
J
Software Engineer for quite a few years. From C programmer to Java web programmer. Very interested in automated testing and functional programming, operating systems, embedded programming.

Java Data Types

Java requires specifying the data type when declaring a variable.
This has two goals:

  • To know how many memory locations are used.

  • To know which operations can be performed on the data. The basic operations are addition, subtraction, multiplication, division and remainder or modulo: +, -, *, /, %.

A primitive type is a data type that is defined directly in the Java programming language.

Primitive types

As in almost all programming languages, Java has the following primitive data types:

  • For integer numbers: byte, short, int, long.

  • For floating‑point numbers: float and double.

  • For characters: char.

  • For logical values (only two possible values): boolean.

In total, Java has eight primitive types: byte, short, int, long, char, float, double and boolean.

String is the only basic predefined data type that is widely used but is not a primitive type; it is a class.

Wrapper types

A wrapper type is a predefined class that encapsulates a value of a primitive type:

  • Integer wraps the primitive type int.

  • Long wraps the primitive type long.

  • Character wraps the primitive type char.

  • Float wraps the primitive type float.

  • Double wraps the primitive type double.

  • Boolean wraps the primitive type boolean.

  • Byte wraps the primitive type byte.

  • Short wraps the primitive type short.

Difference between primitive types and other data types

Primitive types are not objects and therefore do not have an associated class directly.

int monthNumber = 5;
System.out.println(monthNumber);

In data structures that require objects, such as map keys, you must use wrapper types. For example, the key can be of type Integer, but not of type int.

The following is incorrect:

Map<int, String> months = new HashMap<>();   // Incorrrect, because a map's key must be an object

This, however, is correct:

int monthNumber = 5;
Map<Integer, String> months = new HashMap<>();
months.put(monthNumber, "May");

In this and similar cases, the compiler automatically converts the primitive type int into the wrapper type Integer. This technique is called autoboxing (and the reverse conversion is called unboxing).

All Java objects inherit from the Object class. Since primitive types are not objects, they cannot use methods from Object, such as equals(), wait() or notify().

Objects and references

Objects are normally created with the new keyword (or via constructors and factories). Primitive types are declared simply using their type and the variable name.

int monthNumber = 5;
Integer otherMonthNumber = new Integer(5); // nowadays Integer.valueOf(5) is preferred

In other words, a reference‑type variable stores a reference to a memory area where the object is located. A primitive‑type variable stores the value directly in the memory associated with that variable.
A reference is conceptually similar to a pointer in the C language, although in Java it cannot be manipulated explicitly.

Data types and memory

  • The data type that uses the least memory is byte, which occupies only one byte. Its value range goes from -128 to 127.

  • short occupies two bytes and its range goes from -32,768 to 32,767 (Short.MIN_VALUE to Short.MAX_VALUE).

  • int occupies four bytes and its range goes from -2,147,483,648 to 2,147,483,647 (Int.MIN_VALUE to Int.MAX_VALUE).

  • long occupies eight bytes and its range goes from Long.MIN_VALUE to Long.MAX_VALUE.

Notice the symmetry in signed integer types: the total number of negative values is equal to the total number of positive values, considering that zero is counted on the positive side.
Unlike languages such as C or C++, Java does not have unsigned integer types.

  • char occupies two bytes and represents values from 0 to 65,535 (Unicode characters).

  • float occupies four bytes.

  • double occupies eight bytes.

  • The specification does not fix the physical size of boolean; logically it represents only two possible values (true and false).

Syntax for long, float and double literals

  • A long literal may have the suffix L or l (it is recommended to use L to avoid confusion with the digit 1).

  • A float literal must have the suffix F or f.

  • A double literal may have the suffix D or d, although it is usually omitted because double is the default type for floating‑point literals.

long   x = 1L;
float  f = 1.0f;
double y = 1.0;

Digit separator

  • The separator between integer and fractional part is the dot, and it is mandatory in decimal numbers.

  • The thousands separator may be an underscore _. It is optional, but improves readability of large numbers. It was introduced in Java 7.

long   x = 1_000_000L;
double y = 1_000_000.0;

Scientific notation

  • The exponent is written using the letter e or E.

  • In scientific notation, the literal becomes a double by default, unless it is explicitly marked as float.

double y = 1e6;   // 1.0 × 10^6
double z = 1e6D;  // equivalent to the line above
float  f = 1e6f;  // float

(Using e with a long literal is not correct; 1e6 is a floating‑point literal, not a long.)

Precision of decimal numbers

  • To use float, the literal must end with f or F.

  • To use double, you may use d or D, but in practice it is usually omitted, since double is the default type.

float  f = 3.1415927f;
double d = 3.141592653589793;

The float and double types represent floating‑point numbers in binary, and therefore they cannot represent exactly most decimal fractions that are simple in base 10.

Type promotion

Java performs numeric promotion automatically when primitive numeric types are used in expressions with operators like +, -, *, /, %, or in comparisons.

Core Rules

  • byte, short, char → always promoted to int first

  • If any operand is long → both promoted to long

  • If any operand is float → both promoted to float

  • If any operand is double → both promoted to double

byte b = 100;
short s = 200;
// b + s promotes to int (compile error if assigned to byte/short)
int result = b + s;  

long big = 1_000_000L;
int small = 42;
long total = big + small;  // int -> long

BigInteger and BigDecimal classes

The BigInteger and BigDecimal classes are used to represent very large numbers or to perform calculations with high numeric precision.

In the case of BigDecimal, numbers are represented in decimal form (not as binary floating point), which allows arithmetic operations without the typical rounding errors of float and double when working, for example, with money.

// float/double PROBLEM (binary floating point)
double money = 0.1 + 0.2;  // 0.30000000000000004 ❌

// BigDecimal SOLUTION (decimal arithmetic)  
BigDecimal bd1 = new BigDecimal("0.1");
BigDecimal bd2 = new BigDecimal("0.2");
BigDecimal money = bd1.add(bd2);  // 0.3 exactly ✅

BigDecimal stores decimal digits + scale, not fractions p/q or rational numbers: 1/3 becomes "0.333333" (truncated/rounded), not the exact rational 1/3.

  • Therefore, division operations always require scale and rounding modes.

Identity and equality

For primitive types, the value is the data itself; two primitive variables are “equal” if they contain the same value.
For reference types (objects), identity is determined by the reference (the logical address of the object), and logical equality is usually defined by the equals() method.

Autoboxing

As the official documentation says:

The result of all this magic is that you can largely ignore the distinction between int and Integer, with a few caveats. An Integer expression can have a null value. If your program tries to autounbox null, it will throw a NullPointerException. The == operator performs reference identity comparisons on Integer expressions and value equality comparisons on int expressions. Finally, there are performance costs associated with boxing and unboxing, even if it is done automatically.

When to use primitive types

  • The use of primitive types in modern Java is not just about maintaining backward compatibility (autoboxing and unboxing techqniques were introduced in Java 5).

  • In some cases, it's preferable to use primitive types for performance reasons, for example in big loops or in web applications with a very large volume of users.

Kotlin Data Types

Kotlin only works with objects, not with primitives in the Java sense.

  • Types like Int, Long, Double, Boolean, Char, etc. are regular classes from the Kotlin point of view, and you can call methods and properties on them (10.toString()).

  • There is no separate int/Integer distinction in the syntax like in Java; you always write Int, Long, Double, and so on.

What happens at runtime

  • On the JVM, the compiler optimizes these types to real JVM primitives (int, long, double, etc.) whenever possible, so performance is equivalent to using primitives in Java.

  • When a primitive‑like value must behave as an object (for example, when used in a generic type or stored in a List<Int>), the compiler uses the corresponding wrapper (java.lang.Integer, java.lang.Long, etc.), boxing and unboxing automatically. Primitive representations exist only as an implementation detail for efficiency on the JVM platform.

Unsigned types

Kotlin supports unsigned integer types (UInt, ULong, etc.) for positive-only values without sign bit. Java lacks these, as we have explained above.