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

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:
floatanddouble.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:
Integerwraps the primitive typeint.Longwraps the primitive typelong.Characterwraps the primitive typechar.Floatwraps the primitive typefloat.Doublewraps the primitive typedouble.Booleanwraps the primitive typeboolean.Bytewraps the primitive typebyte.Shortwraps the primitive typeshort.
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.shortoccupies two bytes and its range goes from -32,768 to 32,767 (Short.MIN_VALUEtoShort.MAX_VALUE).intoccupies four bytes and its range goes from -2,147,483,648 to 2,147,483,647 (Int.MIN_VALUEtoInt.MAX_VALUE).longoccupies eight bytes and its range goes fromLong.MIN_VALUEtoLong.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.
charoccupies two bytes and represents values from 0 to 65,535 (Unicode characters).floatoccupies four bytes.doubleoccupies eight bytes.The specification does not fix the physical size of
boolean; logically it represents only two possible values (trueandfalse).
Syntax for long, float and double literals
A
longliteral may have the suffixLorl(it is recommended to useLto avoid confusion with the digit 1).A
floatliteral must have the suffixForf.A
doubleliteral may have the suffixDord, although it is usually omitted becausedoubleis 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
eorE.In scientific notation, the literal becomes a
doubleby default, unless it is explicitly marked asfloat.
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 withforF.To use
double, you may usedorD, but in practice it is usually omitted, sincedoubleis 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/Integerdistinction in the syntax like in Java; you always writeInt,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.



