Java Data Types

BEGINNER ~8 min read Tutorial

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.

java
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;
TypeSizeRangeDefaultUse for
byte8-bit-128 to 1270Signed 8-bit data, file bytes
short16-bit-32,768 to 32,7670Memory-tight large arrays
int32-bit-231 to 231-10General-purpose integer
long64-bit-263 to 263-10LLarge counts, timestamps
float32-bit~±3.4e38 (7 sig digits)0.0fGraphics, memory-tight FP
double64-bit~±1.7e308 (15 sig digits)0.0dGeneral-purpose decimal
char16-bit0 to 65,535 (UTF-16)'\u0000'Single Unicode character
boolean1-bit (conceptual)true, falsefalseLogic 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:

java
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.

java
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.

java
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:

java
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:

java
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:

java
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>
}
Avoid var when the type is not obvious

Code 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

  1. Declare one variable of each of the eight primitive types and print each with System.out.println.
  2. Write a one-liner that uses underscores in a literal: int population = 8_100_000_000;. Confirm it compiles.
  3. Create an ArrayList<Integer> and add three int values. Notice autoboxing happens automatically.
  4. Demonstrate a widening cast (int to double) and a narrowing cast (double to int) and print the result of each.