Java Data Types
Every value in Java has a type. The type tells the compiler how much memory to allocate, which operations are valid, and how the value is represented in bytecode. Java's type system is divided cleanly into two halves: eight primitive types that hold raw values, and an open-ended set of reference types that hold pointers to objects.
This tutorial is the definitive reference for choosing the right type for a value. We cover the eight primitives in detail, the wrapper classes that box them, the special type String, and the modern additions var and record. By the end you will be able to read any Java declaration and explain exactly what is being stored.
1. The Eight Primitive Types
Primitive types are the building blocks of every other type. They are stored by value, not by reference, and have fixed sizes regardless of the platform — a key part of the “Write Once, Run Anywhere” promise.
byte b = class="tok-num">100;
short s = class="tok-num">32_000;
int i = class="tok-num">2_000_000_000;
long l = class="tok-num">9_000_000_000L; class=class="tok-str">"tok-cmt">// L suffix required for long literals
float f = class="tok-num">3.14f; class=class="tok-str">"tok-cmt">// f suffix required for float literals
double d = class="tok-num">3.141592653589;
char c = 'J';
boolean ok = true;
| Type | Size | Range | Default | Use for |
|---|---|---|---|---|
byte | 8-bit | -128 to 127 | 0 | Signed 8-bit data, file bytes |
short | 16-bit | -32,768 to 32,767 | 0 | Memory-tight large arrays |
int | 32-bit | -231 to 231-1 | 0 | General-purpose integer |
long | 64-bit | -263 to 263-1 | 0L | Large counts, timestamps |
float | 32-bit | ~±3.4e38 (7 sig digits) | 0.0f | Graphics, memory-tight FP |
double | 64-bit | ~±1.7e308 (15 sig digits) | 0.0d | General-purpose decimal |
char | 16-bit | 0 to 65,535 (UTF-16) | '\u0000' | Single Unicode character |
boolean | 1-bit (conceptual) | true, false | false | Logic flags |
For money and other values where decimal precision matters, do not use double — rounding errors accumulate. Use BigDecimal instead, or store integer cents.
2. Literals
A literal is a source-code representation of a value. Java supports several suffixes and notations:
class=class="tok-str">"tok-cmt">// integer literals
int dec = class="tok-num">255; class=class="tok-str">"tok-cmt">// decimal
int hex = 0xFF; class=class="tok-str">"tok-cmt">// hexadecimal, prefix 0x
int oct = class="tok-num">0377; class=class="tok-str">"tok-cmt">// octal, prefix class="tok-num">0
int bin = 0b11111111; class=class="tok-str">"tok-cmt">// binary, prefix 0b
int grp = class="tok-num">1_000_000; class=class="tok-str">"tok-cmt">// underscore grouping
class=class="tok-str">"tok-cmt">// floating-point literals
double d = class="tok-num">3.14; class=class="tok-str">"tok-cmt">// double is the default
double e = class="tok-num">6.022e23; class=class="tok-str">"tok-cmt">// scientific notation
float f = class="tok-num">3.14f; class=class="tok-str">"tok-cmt">// f suffix for float
class=class="tok-str">"tok-cmt">// char literals
char a = 'A';
char nl = '\n'; class=class="tok-str">"tok-cmt">// escape sequence
char greek = '\u03B1'; class=class="tok-str">"tok-cmt">// unicode escape - alpha
class=class="tok-str">"tok-cmt">// boolean literals
boolean t = true;
boolean f2 = false;
class=class="tok-str">"tok-cmt">// String literals
String s = "Hello, \"Java\"!\n";
Underscores in numeric literals (since Java 7) are purely for human readability; the compiler ignores them. Use them to group digits like you would with thousands separators: 1_000_000 reads as one million.
3. Wrapper Classes
Each primitive has a corresponding reference type, called its wrapper: Integer for int, Boolean for boolean, and so on. Wrappers are needed for two reasons: generic collections can store only objects (you cannot have List<int>), and some library APIs (such as Map.get returning null to mean “absent”) require a nullable representation.
Integer count = Integer.valueOf(class="tok-num">42); class=class="tok-str">"tok-cmt">// explicit
Integer count2 = class="tok-num">42; class=class="tok-str">"tok-cmt">// autoboxing shorthand
int primitive = count.intValue(); class=class="tok-str">"tok-cmt">// explicit unbox
int primitive2 = count; class=class="tok-str">"tok-cmt">// auto-unboxing
Boolean yes = Boolean.TRUE;
Double pi = class="tok-num">3.14;
Character letter = 'A';
class=class="tok-str">"tok-cmt">// common in collections
List<Integer> nums = new ArrayList<>();
nums.add(class="tok-num">1); class=class="tok-str">"tok-cmt">// autoboxes int to Integer
nums.add(class="tok-num">2);
int first = nums.get(class="tok-num">0); class=class="tok-str">"tok-cmt">// auto-unboxes
Java performs autoboxing and unboxing automatically — an int becomes an Integer and back without explicit code. This is convenient, but be aware: Integer a = null; int b = a; throws NullPointerException at runtime.
4. The String Type
String is technically a class, but the language gives it special treatment: you can write string literals in double quotes, and the + operator concatenates strings. Strings are immutable — once created, their contents never change. Operations like toUpperCase() return a brand-new String.
String greeting = "Hello";
String name = "Java";
String message = greeting + ", " + name + "!"; class=class="tok-str">"tok-cmt">// concatenation
class=class="tok-str">"tok-cmt">// message is "Hello, Java!"
class=class="tok-str">"tok-cmt">// Strings are immutable: toUpperCase returns a new String
String upper = message.toUpperCase();
class=class="tok-str">"tok-cmt">// upper is "HELLO, JAVA!" but message is unchanged
class=class="tok-str">"tok-cmt">// StringBuilder for efficient concatenation
StringBuilder sb = new StringBuilder();
for (int i = class="tok-num">0; i < class="tok-num">100; i++) {
sb.append(i).append(",");
}
String result = sb.toString();
For building strings dynamically (in loops, for example), use StringBuilder instead of +. Each + on a String creates a new object; StringBuilder mutates an internal buffer and is much faster.
5. Arrays
An array is a fixed-length, indexed collection of values of the same type. The type is written with square brackets:
int[] nums = {class="tok-num">10, class="tok-num">20, class="tok-num">30};
String[] names = new String[class="tok-num">3];
names[class="tok-num">0] = "Alice";
names[class="tok-num">1] = "Bob";
names[class="tok-num">2] = "Carol";
System.out.println(nums.length); class=class="tok-str">"tok-cmt">// class="tok-num">3
System.out.println(names[class="tok-num">0]); class=class="tok-str">"tok-cmt">// Alice
class=class="tok-str">"tok-cmt">// two-dimensional array
int[][] grid = {
{class="tok-num">1, class="tok-num">2, class="tok-num">3},
{class="tok-num">4, class="tok-num">5, class="tok-num">6}
};
System.out.println(grid[class="tok-num">1][class="tok-num">2]); class=class="tok-str">"tok-cmt">// class="tok-num">6
Arrays have a length field (not a method) and are zero-indexed. They are useful when you know the size up front; for variable-length data, prefer ArrayList.
6. Type Conversion and Casting
Java widens primitive types automatically when no information is lost (int → long → float → double). Narrowing conversions require an explicit cast and may truncate:
class=class="tok-str">"tok-cmt">// widening (automatic)
int i = class="tok-num">42;
long l = i; class=class="tok-str">"tok-cmt">// int -> long, no loss
double d = l; class=class="tok-str">"tok-cmt">// long -> double, no loss (usually)
class=class="tok-str">"tok-cmt">// narrowing (explicit cast)
double pi = class="tok-num">3.14159;
int truncated = (int) pi; class=class="tok-str">"tok-cmt">// class="tok-num">3 - fractional part is dropped
long big = class="tok-num">1_000_000_000L;
int small = (int) big; class=class="tok-str">"tok-cmt">// may overflow silently!
class=class="tok-str">"tok-cmt">// reference cast (along inheritance chain)
Object obj = "Hello";
String s = (String) obj; class=class="tok-str">"tok-cmt">// OK - String IS-A Object
class=class="tok-str">"tok-cmt">// Integer n = (Integer) obj; // runtime ClassCastException!
Between reference types, casting is allowed only along the inheritance chain. A cast does not change the object — it only changes the type the compiler uses to interpret it.
7. var and Type Inference
Since Java 10, you can write var for local variables and let the compiler infer the type. var is not a keyword — it is a reserved type name — so you can still use var as an identifier elsewhere. Use it when the type is obvious from the right-hand side:
var name = "Alice"; class=class="tok-str">"tok-cmt">// String
var count = class="tok-num">42; class=class="tok-str">"tok-cmt">// int
var list = new ArrayList<String>(); class=class="tok-str">"tok-cmt">// ArrayList<String>
var map = new HashMap<String, Integer>(); class=class="tok-str">"tok-cmt">// HashMap<String, Integer>
for (var entry : map.entrySet()) {
class=class="tok-str">"tok-cmt">// entry is Map.Entry<String, Integer>
}
var when the type is not obviousCode like var result = service.compute(); hides the return type from the reader. In that case, write the explicit type. var is a tool for reducing noise, not for hiding information.
Exercises
- Declare one variable of each of the eight primitive types and print each with
System.out.println. - Write a one-liner that uses underscores in a literal:
int population = 8_100_000_000;. Confirm it compiles. - Create an
ArrayList<Integer>and add threeintvalues. Notice autoboxing happens automatically. - Demonstrate a widening cast (
inttodouble) and a narrowing cast (doubletoint) and print the result of each.