If you have already written code in another language, Java will feel both familiar and unexpectedly strict. Here every variable has its type declared in advance, the program won't run until it compiles, and some of the freedoms you're used to are simply forbidden. Let's go over the basics: how to declare a variable, how int differs from Integer, why strings can't be changed, and what control flow is made of.
Why So Strict
Java is a statically typed language. This means the type of every variable is known before the program runs, at compile time. The compiler checks that you aren't adding a number to a list or calling a non-existent method on a string. Errors of this kind are caught immediately, not while the program is running in front of a user.
The short formula: in Java, compilation comes first, then execution. Source code (.java) is turned into bytecode (.class), which the JVM virtual machine executes. That's why a typo in a method name isn't a production crash but a build error right in your editor.
The Entry Point: The main Method
Every Java program starts with the main method. The JVM looks specifically for it and runs it first:
public class App {
public static void main(String[] args) {
System.out.println("Hello, Java"); // print a string to the console
}
}
Let's break the signature down word by word:
public— the method is accessible from outside; the JVM has to be able to see it;static— the method belongs to the class, not to an object, so it can be called without creating an instance;void— the method returns nothing;String[] args— an array of command-line arguments.
For now it's enough to remember this line as the "magic startup incantation". What class, public, and static actually mean is covered in detail in the article on OOP.
Variables and var
A variable is declared using the pattern "type name = value":
int age = 30;
String name = "Anna";
boolean active = true;
The type on the left is mandatory — the compiler has to know what you intend to store. But Java 10 introduced the var keyword: it asks the compiler to infer the type automatically from the right-hand side.
var age = 30; // the compiler sees a number → int
var name = "Anna"; // sees a string → String
var items = new ArrayList<String>(); // the type is clear from the constructor
Important: var is not an "untyped" variable like in dynamic languages. The type is still locked in firmly; you just don't have to write it by hand. You can't later assign a string to age. And var only works where the type is obvious from the value — for local variables inside methods.
Primitives Versus Objects
Java has two worlds of values.
Primitive types are "bare" values that sit directly in memory. There are eight of them, but these are the ones that matter at the start:
int count = 100; // integer, 32 bits
long big = 9_000_000L; // large integer, 64 bits (underscores for readability)
double price = 19.99; // fractional
boolean ok = false; // true / false
char letter = 'A'; // a single character in single quotes
Object (reference) types are everything else: strings, lists, your own classes. The variable holds not the value itself but a reference to an object in memory. Every primitive has an object "twin" — a wrapper: int → Integer, long → Long, boolean → Boolean, double → Double.
Why do wrappers exist? Many parts of the standard library (collections, for example) can only work with objects, not primitives. You can't put an int into a list, but you can put an Integer.
Autoboxing
So you don't have to switch between worlds manually, Java does the conversion itself — this is autoboxing (primitive → wrapper) and unboxing (wrapper → primitive):
Integer boxed = 5; // autoboxing: int 5 → Integer
int back = boxed; // unboxing: Integer → int
Convenient, but there are two traps. First: a wrapper can be null, and trying to unbox null into a primitive will crash the program with a NullPointerException. Second: wrappers must be compared with .equals(), not with == — more on this in the article on exceptions and in the material on collections. The rule is simple: if a value is definitely present and there is just one — use the primitive int; if the value may be absent or has to go into a collection — use Integer.
Strings and Their Immutability
String is an object, and in Java strings are immutable. Any operation that "changes" a string actually creates a new one, while the old one stays the same:
String greeting = "Hello";
String full = greeting + ", world"; // a NEW string was created
// greeting is still "Hello"
This is done for safety and predictability: since a string can't be corrupted, you can freely pass it anywhere and use it as a key. The downside is that if you concatenate thousands of strings with + in a loop, a new object is created every time. For that case there's StringBuilder:
var sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
sb.append(i).append(' '); // we modify the same buffer
}
String result = sb.toString(); // "0 1 2 3 4 "
Two more useful details. Strings should be compared by content with .equals(), not == (the latter compares references). And since Java 15 there are "text blocks" in triple quotes for multi-line text:
String json = """
{
"name": "Anna"
}
""";
Operators
Java's operators are familiar to almost any programmer:
int sum = 2 + 3; // arithmetic: + - * / %
boolean adult = age >= 18; // comparison: == != < > <= >=
boolean both = active && adult; // logic: && (and), || (or), ! (not)
The logical && and || are "lazy": if the left side of && is already false, Java doesn't even evaluate the right side. This is handy for checks like if (user != null && user.isActive()) — the second condition won't run if user is null.
There's also the ternary operator — a short form for choosing one of two values:
String label = adult ? "adult" : "minor";
Control Flow
Branching — if / else if / else:
if (price > 100) {
System.out.println("expensive");
} else if (price > 50) {
System.out.println("moderate");
} else {
System.out.println("cheap");
}
Loops — for, while, and for-each for iterating over collections:
for (int i = 0; i < 3; i++) { // classic counter
System.out.println(i);
}
int n = 3;
while (n > 0) { // while the condition is true
n--;
}
var names = List.of("Anna", "Boris");
for (String person : names) { // for-each: over each element
System.out.println(person);
}
Selection by value — switch. Modern Java has a short arrow form that can return a result directly and doesn't require break:
String role = "admin";
int level = switch (role) {
case "admin" -> 3;
case "editor" -> 2;
default -> 1; // the mandatory "everything else" branch
};
The old form with case ... : and break still works, but the arrow switch is shorter and doesn't let you accidentally "fall through" into the next branch.
In Short
- Java is a statically typed language: the type is known at compile time, and errors are caught before the program runs.
- Every program starts with the
public static void main(String[] args)method. vardoesn't cancel typing — it merely infers the type automatically for local variables.- Primitives (
int,double,boolean) store the value directly; object wrappers (Integer,Double) can benulland are needed for collections. Converting between them is autoboxing. - Strings are immutable: a "change" creates a new object; for heavy concatenation use
StringBuilder, and for comparison use.equals(). - Control flow is standard:
if/else,for,while,for-each, and the modern arrowswitch.
What to Read Next
- OOP in Java: classes, objects, inheritance — what actually lies behind
class,public, andstatic. - Java Collections — where
Integerwrappers and string immutability show up in practice. - The Java Developer's Tools — how to build and run the code we wrote here.