Features of Java Explained: A Beginner’s Guide to Java Basics and Editions
-
Last Updated: July 1, 2023
-
By: javahandson
-
Series
Learn Java in a easy way
The features of java are the reason this language still runs banks, phones, and half the internet after thirty years. Java is simple, object-oriented, secure, and famously platform independent. Write your code once, then run it on Windows, Linux, or a Mac without changing a single line. This guide walks a complete beginner through what Java is, how it runs your code, the JDK/JRE/JVM trio, the three editions, and every core feature with real examples.
Every programming language makes a promise. Java made a bold one back in 1995. Write your program once, and it will run anywhere.
That promise sounded wild at the time. Most languages of that era compiled straight to machine code for one specific chip and one specific operating system. Move the program to a different computer, and you had to compile it all over again. Sometimes it simply refused to work.
Java flipped the whole model. Your code compiles into a middle language called bytecode. A small program on each machine, the Java Virtual Machine, then reads that bytecode and runs it. Windows, Linux, macOS, a phone, a smart card. Same bytecode, same behaviour.
Thirty years later, the promise still holds. Java powers Android apps, bank systems, airline booking engines, Netflix backends, and huge data tools like Hadoop and Kafka. It sits quietly under a big slice of everything you touch online.
We start from zero. No prior coding needed, though a little curiosity helps. Here is the plan:
.java file becomes bytecode, then becomes something a machine runsvar, records, and virtual threadsJava started at Sun Microsystems in 1991. A small team led by James Gosling wanted a language for smart home devices. Think set-top boxes and interactive TV, not laptops.
The team called the project Oak, after a tree outside Gosling’s window. That name already belonged to another company. So the team picked Java instead, apparently while drinking a lot of coffee. Hence the steaming cup logo.
Smart TV never took off in the 1990s. The web did. And a language built to hop between different tiny devices turned out to be perfect for hopping between different computers on the internet. Sun released Java 1.0 in 1995, and it spread fast.
Oracle bought Sun in 2010 and still stewards the language today. Development happens in the open through the OpenJDK project, so anyone can read the source or contribute.
To understand Java, look at the pain it solved. C and C++ ruled the 1990s. Both are powerful, and both hand you a loaded gun.
You manage memory yourself in C++. Forget to free some memory and your program leaks. Free it twice and it crashes. Point at memory you already released and you get a bug that appears on Tuesdays for no clear reason.
Java’s designers cut that whole class of problems. They removed raw pointer arithmetic. They added automatic memory cleanup through garbage collection. Then they wrapped everything in a virtual machine so one compiled program could travel between platforms.
Safety and portability, in short. Those two goals shaped nearly every feature you will read about below.
Beginners mix up a few things early on. Let us clear them now:
This trio confuses almost every newcomer. Three acronyms, three letters each, all starting with J. Let us use a car analogy and it clicks in about a minute.
The JVM, or Java Virtual Machine, is the engine. It takes bytecode and turns it into real instructions for your actual processor.
Think of the JVM as a pretend computer that lives inside your real computer. Your program talks only to this pretend computer. The JVM then handles the messy details of Windows, Linux, or macOS on your behalf.
Each operating system needs its own JVM build. Oracle, Adoptium, and others ship those builds for you. That single fact makes platform independence possible, and we unpack it fully in section 4.
The JVM also does the housekeeping. It allocates memory, runs the garbage collector, checks bytecode for tampering, and speeds up hot code with a just-in-time compiler.
An engine alone will not move a car. You need fuel, wheels, and a chassis. The JRE, or Java Runtime Environment, bundles the JVM together with the standard class library.
That library holds the ready-made building blocks every program leans on. Classes like String, ArrayList, HashMap, Scanner, and System all live there. You never write those from scratch, and that saves an enormous amount of work.
Install only the JRE and you can run Java programs. Try to compile one, though, and you will hit a wall. No compiler ships inside the JRE.
The JDK, or Java Development Kit, is the full workshop. It contains the JRE, and it adds the tools that build software.
Inside the JDK you get:
javac, the compiler that turns .java source into .class bytecodejava, the launcher that starts the JVM and runs your classjar, which packs many class files into one shippable archivejavadoc, which generates HTML documentation from your commentsjdb, a debugger, plus tools like jshell for quick experimentsWriting code? Install the JDK. That is the short version. Since Java 11, Oracle no longer ships a separate JRE download anyway, so the JDK has become the normal choice for everybody.
| Aspect | JVM | JRE | JDK |
|---|---|---|---|
| Full name | Java Virtual Machine | Java Runtime Environment | Java Development Kit |
| Main job | Executes bytecode | Runs Java applications | Builds Java applications |
| Contains | Class loader, memory areas, execution engine, JIT, GC | JVM + standard class library | JRE + compiler + dev tools |
| Has a compiler? | Only the JIT (bytecode to native) | No | Yes, javac |
| Platform specific? | Yes, one build per OS | Yes | Yes |
| You need it to… | Run bytecode at all | Run a finished program | Write and compile code |
| Analogy | Car engine | Full working car | Car plus the garage |
One line to remember: JDK contains the JRE, and the JRE contains the JVM. Three rings, nested neatly inside one another.
Your Java journey has two steps, not one. First you compile. Then you run.
Step one belongs to javac. It reads Hello.java, checks your grammar, catches your typos, and produces Hello.class. That new file holds bytecode, not English and not machine code.
Step two belongs to the java launcher. It boots a JVM, loads Hello.class, verifies the bytecode, and executes it. Only at this moment does your program actually do anything.
javac Hello.java // produces Hello.class (bytecode) java Hello // JVM loads and runs Hello.class
Compare that with C++. There, the compiler goes straight to machine code for one chip and one OS. Fast, yes. Portable, not at all.
Bytecode sits in the middle. It is too low-level for a human to enjoy, and too high-level for a CPU to execute directly. Perfect middle ground.
Curious what it looks like? The javap tool disassembles a class file for you:
javap -c Hello // Output (trimmed): // 0: getstatic #2 // Field java/lang/System.out // 3: ldc #3 // String Hello, World! // 5: invokevirtual #4 // Method println // 8: return
Notice how short those instructions are. Push a value, call a method, return. Every JVM on earth understands that same small vocabulary, which is precisely the point.
Sun coined the slogan “Write Once, Run Anywhere”, shortened to WORA. Here is the trick behind it.
Bytecode targets the JVM, never a real processor. So a .class file carries no assumptions about Intel, ARM, Windows, or Linux. Copy that file to any machine with a JVM and it just runs.
Where does the platform-specific work go? Straight into the JVM itself. Someone already built a JVM for Windows, another for Linux, another for macOS, another for ARM chips. Each one speaks bytecode on the outside and native machine code on the inside.
Picture a translator at the United Nations. Every delegate speaks one shared language into the microphone. Each listener hears it in their own tongue. Bytecode is that shared language, and the JVM is the translator.
Tradition demands it. Create a file named Hello.java and type this in:
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
// Output: Hello, World!Four lines of ceremony for one line of work. Beginners groan at that, and fairly so. Every word earns its place, though, so let us walk through them.
public class Hello declares a class named Hello. Java wraps all code inside classes, so this is your container. The file name must match the public class name exactly, hence Hello.java.main is the front door. When you type java Hello, the JVM hunts for this exact method and starts there.public lets the JVM call main from outside your class. Make it private and the program will not start.static means the method belongs to the class, not to an object. Handy, because no object exists yet when your program begins.void promises that main hands nothing back when it finishes.String[] args catches any command-line arguments you pass. Run java Hello world and args holds “world”.System.out.println(...) prints a line to the console. System is a class, out is the output stream, and println writes text plus a newline.Case matters everywhere. Write Public or Main and the compiler will refuse. Java treats uppercase and lowercase as completely different letters.
Open a terminal in the folder holding your file. Then run two commands:
javac Hello.java java Hello // Output: Hello, World!
Notice the second command. You type java Hello, not java Hello.class. The launcher wants a class name, and it adds the extension itself.
Since Java 11 you can also skip compilation for a quick test. One command runs a single source file directly:
java Hello.java // Output: Hello, World! // Compiles in memory, writes no .class file
Great for learning and tiny scripts. For anything real, keep compiling properly with javac or a build tool such as Maven or Gradle.
Java ships in three flavours. Each targets a different kind of machine, and each builds on the one before it.
Java SE forms the core. Every other edition sits on top of it, so this is the one you learn first.
Java SE gives you the language itself plus the essential libraries:
String, Integer, arrays, and the primitivesArrayList, HashMap, and HashSetjava.io and java.nioDesktop apps, command-line tools, and the backbone of every server framework all rest on Java SE. When someone says “I know Java”, they mean this.
Big companies need more than the basics. They need web servers, database transactions, messaging queues, and security across dozens of services.
Java EE added exactly those pieces on top of Java SE. Servlets, JSP, JPA for databases, JMS for messaging, and dependency injection all arrived through this edition.
One important naming update: Oracle handed the project to the Eclipse Foundation in 2017, and it now carries the name Jakarta EE. Packages moved from javax.* to jakarta.*, which explains a lot of confusing tutorials online.
Worth knowing too: most modern teams reach for Spring Boot rather than a full Jakarta EE server. Spring borrows the same ideas, though, so the concepts still transfer.
Java ME shrinks Java down for tiny hardware. Feature phones, SIM cards, sensors, TV boxes, and embedded controllers all fall in its lane.
Memory is scarce on such devices, so Java ME trims the standard library hard. You get a subset, not the full toolbox.
Honestly, Java ME matters far less today. Smartphones killed the feature phone, and Android took the mobile crown. Interviewers still ask about the three editions, so keep the name handy.
Older books and blogs write J2SE, J2EE, and J2ME. Those names belong to the past.
Sun retired the “J2” prefix back in 2004 with the release of Java 5. Ever since, the correct names have been Java SE, Java EE (now Jakarta EE), and Java ME. If a tutorial still says J2EE, treat it as a hint that the content is roughly twenty years old.
Sun published a white paper describing Java with a list of buzzwords. Those buzzwords became the classic features of java that every interviewer still asks about. Here they are, with what each one really means in practice.
Simple does not mean easy. It means Java deliberately left out the sharpest tools in C++.
Gone: pointer arithmetic, manual free(), operator overloading, multiple inheritance of classes, and header files. Each of those features caused far more bugs than it prevented.
Take memory. A C++ programmer allocates and releases it by hand. A Java programmer creates an object and walks away, because the garbage collector cleans up unreachable objects automatically.
String name = new String("Java");
name = null; // old object now unreachable
// Garbage collector reclaims it later. No free(), no delete.Familiar syntax helps as well. Anyone coming from C or C++ recognises the braces, loops, and semicolons immediately.
Java models your program as objects. An object bundles data with the behaviour that acts on that data.
Think about a real car. It has state, such as colour and current speed. It also has behaviour, such as accelerate and brake. A Java class captures both in one place.
class Car {
private int speed = 0; // state
void accelerate(int amount) { // behaviour
speed += amount;
}
int getSpeed() { return speed; }
}
Car car = new Car();
car.accelerate(40);
System.out.println(car.getSpeed()); // Output: 40Four pillars hold this up: encapsulation, inheritance, polymorphism, and abstraction. Notice private int speed above. Outside code cannot touch that field directly, so the class guards its own data. That is encapsulation in one line.
Here sits the headline feature. Compile once, and the resulting bytecode runs on any machine with a JVM.
Compile Payroll.java on your Windows laptop. Email the .class file to a colleague running Linux. Send the same file to a server on ARM hardware. Nobody recompiles anything, and all three see identical output.
C++ cannot do that. A C++ binary built for Windows on Intel will not launch on Linux, and it certainly will not launch on ARM. You compile again for each target.
Java pays a small price for this. An extra translation layer costs a little speed at startup, though the JIT compiler claws most of it back, as section 7.7 explains.
Security shows up at several layers, and most of it happens without you asking.
int[] numbers = new int[3]; numbers[5] = 99; // Output: Exception in thread "main" // java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3
An exception feels annoying. Compare it with C, where that same write silently corrupts memory next door and crashes an hour later. Loud failure beats quiet corruption every time.
Robust means the language pushes you to handle failure instead of ignoring it.
Three mechanisms carry most of the weight. Strong type checking catches mistakes while you compile. Exception handling gives errors a proper channel. Garbage collection removes dangling-pointer bugs entirely.
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero!");
} finally {
System.out.println("Cleanup always runs.");
}
// Output: Cannot divide by zero!
// Output: Cleanup always runs.Checked exceptions push the idea further. Open a file and the compiler forces you to deal with IOException. You may dislike the nagging, yet it stops errors from slipping through unnoticed.
A thread is a separate line of work inside one program. Java baked threads into the language from day one, long before rivals did.
Why bother? Picture a music app. One thread plays audio, another downloads the next track, a third redraws the screen. Without threads, the music would stutter every time a download started.
Thread worker = new Thread(() -> {
System.out.println("Downloading in the background...");
});
worker.start();
System.out.println("Main thread stays responsive.");
// Output: Main thread stays responsive.
// Output: Downloading in the background...
// (order can vary, both run at once)Modern servers depend on this. Thousands of users hit a website at once, and each request rides its own thread. Keywords like synchronized and the java.util.concurrent package keep those threads from stepping on each other.
Java earned a “slow” reputation in 1997. That label expired long ago, and the JIT compiler is why.
JIT stands for just-in-time. As your program runs, the JVM watches which methods run most. Those hot methods get compiled from bytecode into native machine code, then cached. Later calls skip interpretation completely.
Here is the surprising part. A JIT compiler sees real runtime behaviour, which an ahead-of-time compiler never can. It knows which branch you actually take and which method you actually call. Armed with that, it inlines aggressively and sometimes beats a static compile.
Real proof? Netflix, Uber, and most large banks run Java at massive scale. None of them would tolerate a slow platform.
Distributed means Java expects your program to talk across a network. That assumption goes right back to its internet-era birth.
Networking lives in the standard library, not in some add-on you hunt down. Sockets, URLs, HTTP clients, and remote method invocation all ship in the box.
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.github.com"))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode()); // Output: 200Microservices push this idea to its limit. Split one giant app into twenty small services, spread them across machines, and let them chat over HTTP. Java handles that pattern comfortably, which explains its grip on enterprise backends.
Dynamic means Java decides some things while running, not while compiling.
Classes load lazily. The JVM pulls in a class the first moment your code needs it, not before. So a plugin dropped into a folder can join a running application without a rebuild.
Reflection takes this further. Your code can inspect a class, list its methods, and call them by name at runtime:
Class<?> c = Class.forName("java.lang.String");
System.out.println(c.getSimpleName()); // Output: String
System.out.println(c.getMethods().length > 0); // Output: trueFrameworks live on this. Spring scans your classes, spots the annotations, and wires everything together at startup. Reflection makes that magic possible.
Portable and platform independent sound like twins. They differ in one important way.
Platform independence comes from bytecode plus the JVM. Portability comes from the language rules themselves. Java nails down the size and behaviour of every primitive type, and it does so identically on every machine.
int holds 32 bits everywhere, from a Raspberry Pi to a mainframelong holds 64 bits, alwayschar holds 16 bits and stores Unicode, so other alphabets work out of the boxC tells a different story. There, an int might be 16 bits, 32 bits, or 64 bits depending on the compiler. That ambiguity has burned countless programmers porting code.
Android grew up on Java. Billions of phones run apps whose logic began as Java source.
One twist matters here. Android does not use the standard JVM. It compiles your bytecode again into DEX format, which the ART runtime executes on the device.
Kotlin now leads for new Android projects, and Google recommends it. Kotlin runs on the same JVM foundations, though, and mixes freely with Java. Learn Java first and Kotlin costs you a weekend.
Java owns the enterprise backend, and the grip is not loosening.
Where exactly?
Spring Boot dominates this space. It handles configuration, database access, security, and REST APIs, so a small team can ship a production service in days.
Open the big-data toolbox and you will find Java everywhere inside it.
Cloud platforms lean on it too. AWS Lambda, Google Cloud Functions, and Azure all support Java as a first-class runtime.
Java used to move slowly. Years passed between versions, and features piled up waiting for the next big bang.
Everything changed in 2018. Java now ships a new version every six months, like clockwork. March and September, without fail.
Smaller releases arrive more often, which suits everyone. Features land when they are ready instead of waiting three years for a mega-release.
Nobody upgrades a banking system twice a year. So Oracle marks certain versions as LTS, meaning Long-Term Support.
An LTS release gets security patches and bug fixes for many years. Companies plant themselves on one and stay put.
| Version | Year | Status | Headline feature |
|---|---|---|---|
| Java 8 | 2014 | LTS, still common in legacy systems | Lambdas and streams |
| Java 11 | 2018 | LTS | New HTTP client, run a source file directly |
| Java 17 | 2021 | LTS, widely adopted | Records, sealed classes, pattern matching |
| Java 21 | 2023 | LTS, current favourite | Virtual threads |
| Java 25 | 2025 | LTS, newest | Compact source files, scoped values |
Starting fresh today? Pick Java 21 or Java 25. Both give you modern syntax plus years of support ahead.
Old tutorials make Java look wordy. Recent versions cut a lot of that noise. Three additions stand out.
1. var for local variables (Java 10). The compiler infers the type, so you stop repeating yourself. Java stays statically typed, and names below is still firmly a List<String>.
// Old way
Map<String, List<Integer>> scores = new HashMap<String, List<Integer>>();
// With var
var names = new ArrayList<String>();
names.add("Ada");
System.out.println(names); // Output: [Ada]2. Records (Java 16). A record is a small class that only carries data. One line replaces fifty lines of getters, constructors, equals, hashCode, and toString.
record Point(int x, int y) { }
Point p = new Point(3, 4);
System.out.println(p.x()); // Output: 3
System.out.println(p); // Output: Point[x=3, y=4]3. Virtual threads (Java 21). Regular threads map to operating-system threads, and each one eats about a megabyte of memory. Spawn ten thousand and your server groans. Virtual threads are lightweight and managed by the JVM, so a million of them fit comfortably.
Thread.startVirtualThread(() -> {
System.out.println("Cheap thread, huge scale");
});
// Output: Cheap thread, huge scaleDo not worry about mastering these on day one. Just recognise them, because modern codebases use all three heavily.
Interviewers love this comparison. Java borrowed C++ syntax, then dropped the parts that hurt.
| Aspect | Java | C++ |
|---|---|---|
| Compiles to | Bytecode, then JIT to native | Native machine code directly |
| Portability | Same .class runs anywhere | Recompile per platform |
| Memory management | Automatic garbage collection | Manual new / delete |
| Pointers | References only, no arithmetic | Full pointer arithmetic |
| Multiple inheritance | Interfaces only | Yes, for classes too |
| Operator overloading | Not supported | Supported |
| Typical speed | Very fast after JIT warm-up | Fastest, predictable |
| Best suited for | Web, Android, enterprise, big data | Games, drivers, embedded, engines |
Java forgives more. Forget to free memory and nothing terrible happens. Index past the end of an array and you get a clear exception, not a mystery crash three functions later.
C++ rewards precision and punishes carelessness. It gives you raw control over every byte, which games and device drivers genuinely need.
Neither language is better in the abstract. They aim at different jobs. Building a web backend or an Android app? Java. Squeezing every microsecond from a game engine? C++.
This one still causes chaos in interviews. Java and JavaScript share four letters and nothing else.
Netscape built JavaScript in 1995 for browsers. Sun built Java for devices and servers. The two teams struck a marketing deal, and JavaScript got its name purely to ride Java’s popularity.
As the old joke goes, Java is to JavaScript what car is to carpet.
The “Java is slow” line comes from the late 1990s, when the JVM interpreted every instruction. That criticism was fair back then.
Today the JIT compiler produces native code for hot paths, and garbage collectors pause for mere milliseconds. High-frequency trading firms, of all people, run Java. Speed clearly is not stopping them.
Purists point out a real gap. Java has eight primitive types, and int, char, boolean, and friends are not objects.
Why keep them? Raw speed. An int occupies four bytes with no object overhead, and that matters inside a tight loop.
Wrapper classes like Integer bridge the gap when you need an object, and autoboxing converts between the two automatically. So the answer to “is Java fully object-oriented” is no, and that trade-off was intentional.
A: The JVM executes bytecode and translates it into native instructions for your machine. Around that engine sits the JRE, which bundles the JVM with the standard class library so it can run a finished Java program. Developers then need the JDK, because it wraps the JRE and adds tools such as javac, jar, and the debugger. Simply put, JDK contains JRE, and JRE contains JVM.
A: The javac compiler produces bytecode rather than machine code. Bytecode targets the JVM, so it carries no assumption about the chip or the operating system. Copy that same .class file to Windows, Linux, or macOS, and the local JVM runs it without a recompile. The JVM itself differs per platform, which is exactly what makes your code portable.
A: Ten features define the language: simple, object-oriented, platform independent, secure, robust, multithreaded, high performance, distributed, dynamic, and portable. Platform independence draws the most attention, because bytecode plus the JVM lets one compiled file run anywhere. Automatic garbage collection and built-in threading rank close behind in everyday value.
A: Both, actually. The JVM starts by interpreting bytecode, which keeps startup fast. Meanwhile it counts how often each method runs. Once a method turns hot, the just-in-time compiler converts it to native machine code and caches the result. That hybrid approach gives you quick startup plus near-native speed later on.
A: Java SE, the Standard Edition, holds the core language and libraries. Jakarta EE, formerly Java EE, adds enterprise pieces like servlets, JPA, and messaging for large server applications. Java ME, the Micro Edition, targets small embedded devices. Watch out for the old J2SE, J2EE, and J2ME labels, because Sun dropped that naming back in 2004.
A: Bytecode is the intermediate instruction set inside a .class file. It sits between your source code and native machine code. Humans find it hard to read, and a CPU cannot run it directly, so the JVM steps in as translator. You can inspect it yourself with the javap -c command.
A: Choose an LTS release, so you get years of updates. Java 21 or Java 25 both work well today, and Java 17 remains a safe bet for older tutorials. Grab a free OpenJDK build from Adoptium or Amazon Corretto. Always install the JDK, never just the JRE, because you need javac to compile.
A: No, and the reason is speed. Eight primitive types, including int, char, and boolean, are not objects. They skip object overhead, which pays off inside tight loops. Wrapper classes such as Integer cover the cases where you truly need an object, and autoboxing converts between the two for you.
Let us pull the threads together. Java began in 1991 as a language for small devices, then found its real home on the internet.
Your source code compiles to bytecode, and the JVM turns that bytecode into native instructions on whatever machine it lands. One compile, many platforms. That single design choice explains most of the features of java you just read about.
Keep the trio straight: the JDK builds programs, the JRE runs them, and the JVM executes the bytecode underneath. Install the JDK and you have everything.
Three editions serve three worlds. Java SE covers the core, Jakarta EE handles enterprise systems, and Java ME lives on tiny hardware. Ignore any tutorial still writing J2EE.
Ten features made Java famous, and each one solves a real problem. Platform independence gives you reach. Garbage collection saves you from memory bugs. Threads and the JIT compiler deliver serious performance at scale.
Ready for the next step? Install a modern LTS JDK, write that Hello World file, and compile it yourself. Nothing beats seeing the output appear in your own terminal.