Careers[careers]
Java vs C++: what differs in the code, and which to choose
- Section
- Careers [careers]
- Standard
- C++23
- Compilers
- MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2, Clang 23.1 (libc++), JDK 27
- Published
- Length
- 15 min read
In this answer10 clauses
- 1Java vs C++ at a glance
- 2Values and references: what
=and==do - 3Memory: a destructor or a garbage collector
- 4What a mistake does: undefined in C++, defined in Java
- 5C++ templates and Java generics
- 6What else differs between Java and C++
- 7Is C++ faster than Java?
- 8Using Java and C++ together
- 9Where Java and C++ are used
- 10Java or C++: which to choose, or learn first
Abstract
Java vs C++: C++ compiles to machine code, keeps objects as values destroyed at a known point, and leaves signed overflow, an index past the end and a null pointer dereference undefined. Java compiles to bytecode that a JVM runs and compiles further, reaches objects through references (a garbage collector frees unreachable ones), and gives those mistakes a defined result: a wrap or an exception. Choose C++ to control memory layout, latency and the hardware; choose Java for server software where the JVM’s safety, tools and libraries matter more.

Java vs C++: they look alike and work differently. C++ compiles to machine code, keeps objects as values destroyed at a known point, and leaves many mistakes undefined: compilers assume they never happen. Java compiles to bytecode that a JVM runs and compiles further, reaches objects through references (a garbage collector frees unreachable ones), and defines those mistakes: an int wraps, a bad index or a null throws. Choose C++ to control memory layout, latency and the hardware, Java for server software where the JVM’s safety matters more. =, == and new mean different things in each.
This comparison is written for C++23 and for Java as JDK 27 ships it (general availability 15 September 2026; Java 25 is the latest long-term support release). Reading an uninitialized local is undefined behavior in C++23; C++26 makes it erroneous behavior ([basic.indet]) unless the variable is declared [[indeterminate]], and no compiler’s warning mentioned erroneous behavior in C++26 mode.
The C++ samples were built as C++23 by four compilers: MSVC 19.44 and Clang 19.1 on Microsoft’s STL (Windows x64), GCC 16.2 with libstdc++ and Clang 23.1 with libc++ (Compiler Explorer, Linux x86-64). The Java samples were compiled by javac with -Xlint:all and run on JDK 27 at Compiler Explorer. The compilers and their libraries have their own page; Since When dates each C++ feature.
Java vs C++ at a glance
| Subject | C++ | Java |
|---|---|---|
| Built and run | compiled to machine code before it runs | compiled to bytecode, which a JVM interprets and compiles while it runs |
| A variable of class type | is the object | holds a reference to one |
| Freeing memory | a destructor, where the owner’s scope ends | a garbage collector, some time after the last reference goes |
a == b on objects |
what your operator== compares |
whether both name the same object |
| Signed overflow, a bad index, a null | undefined behavior | a wrap or an exception |
| Generic code | templates, instantiated per type | generics, erased to one class |
| Inheritance | several base classes | one superclass, many interfaces |
| Enforced by the compiler | no change through a const access path, except to mutable members |
checked exceptions are handled; a local is set before it is read |
The “Built and run” row is the usual route, not the only one: GraalVM’s Native Image compiles Java ahead of time to a native executable.

The same program in both, five readings and the total of those above a limit, reads almost line for line:
struct Reading {
std::string sensor;
double value;
};
int main()
{
std::vector<Reading> readings{{"t1", 21.5}, {"t2", 19.0},
{"t3", 23.25}, {"t4", 18.5}, {"t5", 22.75}};
double limit = 20.0;
double total = 0.0;
for (const Reading& r : readings)
if (r.value > limit)
total += r.value;
std::println("total above the limit: {}", total);
}— end example]
record Reading(String sensor, double value) {}
class Main {
public static void main(String[] args) {
List<Reading> readings = List.of(new Reading("t1", 21.5),
new Reading("t2", 19.0), new Reading("t3", 23.25),
new Reading("t4", 18.5), new Reading("t5", 22.75));
double limit = 20.0;
double total = 0.0;
for (Reading r : readings)
if (r.value() > limit)
total += r.value();
System.out.println("total above the limit: " + total);
}
}— end example]
The Java Reading is a record (since Java 16), and r.value() is its accessor. Both printed:
total above the limit: 67.5— end example]
Values and references: what = and == do
In Java, new is how objects are made: new Reading("t1", 21.5) yields a reference to one. In C++, Reading a{"t1", 21.5}; is the object itself; new returns a pointer to an object that lives until something calls delete, and current code writes a local object or std::make_unique instead (Core Guidelines R.11: “Avoid calling new and delete explicitly”).
So in C++, Reading b = a; copies the object. In Java it copies the reference, and a change through b shows through a. Here the Java Reading is a plain class with two fields:
Reading a{"t1", 21.5};
Reading b = a; // copies the object
b.value = 99.5;
std::println("a.value = {}", a.value); // 21.5: a is untouched
std::println("b.value = {}", b.value); // 99.5— end example]
Reading a = new Reading("t1", 21.5);
Reading b = a; // copies the reference
b.value = 99.5;
System.out.println("a.value = " + a.value); // 99.5: a too
System.out.println("b.value = " + b.value); // 99.5— end example]
C++ printed a.value = 21.5, Java a.value = 99.5. A container shows the same split:

On two references, Java’s == is “object equality” (JLS chapter 15, 15.21.3): the same object or not. Contents are compared by equals, where the class provides one: a record’s compares the members. Below, p and q belong to a plain class with the same two fields and no equals of its own, and s and t are two new String("t1"):
Reading a = new Reading("t1", 21.5);
Reading b = new Reading("t1", 21.5);
System.out.println("a == b: " + (a == b)); // false
System.out.println("a.equals(b): " + a.equals(b)); // true
PlainReading p = new PlainReading("t1", 21.5);
PlainReading q = new PlainReading("t1", 21.5);
System.out.println("p.equals(q): " + p.equals(q)); // false
Integer small = 127, sameSmall = 127;
Integer large = 128, sameLarge = 128;
System.out.println("127 == 127: " + (small == sameSmall));
System.out.println("128 == 128: " + (large == sameLarge));
String s = new String("t1");
String t = new String("t1");
System.out.println("s == t: " + (s == t));
System.out.println("s.equals(t): " + s.equals(t));— end example]
It printed:
a == b: false
a.equals(b): true
p.equals(q): false
127 == 127: true
128 == 128: false
s == t: false
s.equals(t): true— end example]
Two boxed Integers were one object at 127 and two at 128: JLS 5.1.7 guarantees one object only when an int constant from -128 to 127 is boxed, and for other values “disallows any assumptions about the identity of the boxed values”. javac -Xlint:all warned about none of these == lines.
In C++ a struct has no == until you declare one: all four rejected a == b (GCC 16.2: no match for 'operator=='). Since C++20, bool operator==(const Reading&) const = default; compares the members: a == b printed true, and &a == &b, the C++ spelling of Java’s ==, printed false.
Java passes every argument by value: the argument’s value initializes a new parameter variable (JLS 8.4.1), and for an object that value is the reference. A method that assigned a new Reading to its parameter left the caller’s a at t1 21.5; one that set r.value = 99.5 changed it to t1 99.5. In C++ the function chooses: through a Reading parameter both changes were lost, through a Reading& both reached the caller (pass by value or by const reference).
Memory: a destructor or a garbage collector
A local C++ object is destroyed where its scope ends, in reverse order of construction, on every way out, including an exception that is caught, and its destructor releases what it owns: memory, a file, a lock. With no handler anywhere, whether the stack is unwound is implementation-defined, and none of the four compilers ran the destructors. That is RAII. std::unique_ptr and std::vector are owners of this kind, not a garbage collector (the rule of zero).
A Java object is freed some time after nothing refers to it. Memory is the collector’s job; a file or a socket is yours, and try-with-resources closes an AutoCloseable at the end of its block. Each Resource below prints a line when it is opened, used and closed:
~Resource() { std::println("close {}", name_); }
// ...
void work(bool fail)
{
Resource log("log");
Resource port("port");
log.use();
port.use();
if (fail)
throw std::runtime_error("sensor offline");
} // port, then log, closed here: on both paths— end example]
static void work(boolean fail) {
try (Resource log = new Resource("log");
Resource port = new Resource("port")) {
log.use();
port.use();
if (fail)
throw new IllegalStateException("sensor offline");
} // port, then log, closed here: on both paths
}
static void forgotten() {
Resource log = new Resource("log");
Resource port = new Resource("port");
log.use();
port.use();
} // nothing closes them— end example]
On the exception path both printed:
open log
open port
use log
use port
close port
close log
caught: sensor offline— end example]
forgotten() printed two open and two use lines and no close, and -Xlint:all did not warn. Nor does a collector prevent every leak: an object that is still referenced is never freed.
What a mistake does: undefined in C++, defined in Java
Signed overflow, [] past the end, a null pointer dereference and integer division by zero are undefined behavior in C++: compilers optimize on the assumption that they do not happen, so a wrong program may do anything, not merely crash. Java gives each a defined result and refuses to compile a read of an unset local:
| Mistake | Java | C++ |
|---|---|---|
int overflow |
wraps (JLS 4.2.2); Math.addExact throws |
undefined ([expr.pre]) |
| Index 3 of three elements | Array (JLS 10.4) |
readings[3] undefined; at(3) throws std::out_of_range |
| A call through a null | NullPointerException |
undefined through a pointer; a reference or a value cannot be null |
| A local read before it is set | does not compile (JLS 16) | compiles, with or without a warning; undefined (erroneous in C++26) |
int division by zero |
ArithmeticException: / by zero |
undefined |
For overflow, [expr.pre] makes the behavior undefined when a result is not in the range of its type; the JLS says “The integer operators do not indicate overflow or underflow in any way” (4.2.2):
int total = std::numeric_limits<int>::max();
std::println("{}", total + 1); // undefined: no value to report— end example]
int total = Integer.MAX_VALUE;
System.out.println(total + 1); // wraps
System.out.println(Math.addExact(total, 1)); // throws— end example]
The C++ line was built and never run; no compiler warned about it at -O0 or -O2. Java printed the wrapped value, then the exception:
-2147483648
Exception in thread "main" java.lang.ArithmeticException: integer overflow— end example]
Two tools make the overflow visible in C++:
- Built with
-fsanitize=, GCC 16.2 and Clang 23.1 reportedsigned-integer-overflow runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'. - In a constant expression it is an error. All four rejected the overflow inside a
constexprfunction call (Clang:value 2147483648 is outside the range of representable values of type 'int'); written directly asconstexpr int next = total + 1;, MSVC 19.44 compiled it with warning C4307 and the wrapped value.
readings[3] drew no warning and was built only. readings.at(3) threw std::out_of_range on all three libraries, and with a library’s hardening switch on, readings[3] stopped the program (bounds and at() in std::vector):
| Library | at(3) what() |
Hardening switch | readings[3], hardened |
|---|---|---|---|
| Microsoft’s STL | invalid vector subscript |
_MSVC_STL_HARDENING=1 |
stopped, no message |
| libstdc++ | vector:: |
-D_GLIBCXX_ASSERTIONS |
Assertion '__n < this->size()' failed., then SIGABRT |
| libc++ | vector |
-D_LIBCPP_HARDENING_MODE= |
stopped, no message |
C++26 gives a[n] the hardened precondition n < a.size() ([sequence.reqmts], P3471R4): a hardened implementation checks it and ends the program, and in any other a violation stays undefined. Java’s array threw java.; ArrayList.get(3) threw Index, the array exception’s superclass.
For a null, JDK 27 names the call that returned it:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Reading.value()" because the return value of "Main.find(java.util.List, String)" is null— end example]
In C++, a lookup that may find nothing can return a std::optional, and its value() threw std:: on all four.
javac rejected the unset total with error: variable total might not have been initialized. The C++ copy compiled; with the read inside a loop, MSVC (C4700) and both Clangs warned, while GCC 16.2 was silent without optimization and said 'total' may be used uninitialized at -O2.
C++ templates and Java generics
A C++ template is instantiated: the compiler writes a separate function for each type argument, so T{} makes a T and a std::vector<int> holds ints (C++ templates):
template <typename T>
T sum(const std::vector<T>& values)
{
T total{}; // a T: 0, 0.0 or an empty string
for (const T& v : values)
total += v;
return total;
}
int main()
{
std::println("{}", sum(std::vector<int>{3, 4})); // 7
std::println("{}", sum(std::vector<double>{21.5, 19.0})); // 40.5
std::println("{}", sum(std::vector<std::string>{"t1", "t2"}));
}— end example]
Java generics are erased (JLS 4.6): one class serves every type argument. Type arguments must be reference types (4.5.1), so List<int> does not compile and a List<Integer> holds boxed Integers:
static <T extends Number> double sum(List<T> values) {
double total = 0.0;
for (T v : values)
total += v.doubleValue();
return total;
}
// ...
List<Integer> counts = new ArrayList<>(List.of(3, 4));
List<Double> values = new ArrayList<>(List.of(21.5, 19.0));
System.out.println(sum(counts)); // 7.0
System.out.println(sum(values)); // 40.5
System.out.println(counts.getClass() == values.getClass());
System.out.println(counts.getClass().getName());— end example]
The template printed 7, 40.5 and t1t2. The Java method printed 7.0 (its sum returns a double) and 40.5, then true and java.util.ArrayList: at run time both lists are one class. javac rejected List<int> and new T() with the same headline, error: unexpected type, and obj instanceof List<String> on an Object with error: Object cannot be safely cast to List<String> (at run time a List<String> cannot be told from a List<Integer>).
Value objects (JEP 401) are integrated as a preview for Java 28, which is still in development; generics over primitive types (JEP 218) are a candidate, not delivered.
What else differs between Java and C++
| Feature | C++ | Java |
|---|---|---|
| Operator overloading | Volts operator+ compiled and printed 4.75 |
none: bad operand types for binary operator '+' |
| Multiple inheritance | struct Thermometer : Sensor, Display |
one superclass and many interfaces; extends Sensor, Display does not parse |
| Unsigned integers | std::uint32_t holds 4294967295 |
no unsigned int; Integer.toUnsignedString printed 4294967295 |
| Checked exceptions | none | must be caught or declared (JLS 11): unreported exception IOException |
| Free functions | yes | every method in a class; in a compact source file (Java 25) the class is declared implicitly |
| Threads | std::thread, and std::jthread since C++20 |
threads, and virtual threads since Java 21 |
Virtual functions and std::thread and std::jthread have their own answers.
const and final take the most care: C++ has one answer, Java three. A const vector cannot change, and the compiler checks it:
const std::vector<int> limits{20, 25};
limits.push_back(30); // error: the vector is const— end example]
All four rejected it. GCC 16.2:
06c-const.cpp:11:21: error: passing 'const std::vector<int>' as 'this' argument discards qualifiers [-fpermissive]— end example]
A final variable cannot be reassigned, but the list it refers to can change:
final List<Integer> limits = new ArrayList<>(List.of(20, 25));
limits.add(30); // fine: the list is not final— end example]
It printed [20, 25, 30]; limits = new ArrayList<>(); was error: cannot assign a value to final variable limits. An unmodifiable list compiles the same call and refuses it when it runs:
List<Integer> limits = List.of(20, 25);
limits.add(30); // compiles; throws when it runs— end example]
It threw:
Exception in thread "main" java.lang.UnsupportedOperationException— end example]
In Java, final belongs to the variable and an unmodifiable list’s refusal to the object, found at run time; C++’s const belongs to the type and is checked by the compiler.
Is C++ faster than Java?
Nothing was timed for this comparison. The Benchmarks Game measured its fastest C++ (g++ 14.2) and Java (Java 23) programs on a quad-core 3.0 GHz i5-3330 under Ubuntu 24.04, the C++ ones in January 2025 and the Java ones in February 2025:
| Program | Fastest C++ | Fastest Java |
|---|---|---|
| n-body | 2.15 s | 6.92 s |
| fannkuch-redux | 3.33 s | 10.48 s |
| spectral-norm | 0.72 s | 1.61 s |
| Memory | about 2 to 6 MB | about 60 MB |
The site flags those three C++ programs for “possible hand-written vector instructions or “unsafe” or naked ffi”; its fastest unflagged C++ programs took 5.15 s, 7.86 s and 1.53 s. Its own warning: “How the programs are written matters! Always look at the source code.”
What the languages decide: C++ sets the layout and runs with no collector and no JVM to start. sizeof(Reading) for an int id and a double value was 16 on all four, and neighbours in a std::vector<Reading> were 16 bytes apart: contiguous. Java has no sizeof, so nothing was measured on its side; its JIT compiles with knowledge of the running program.
Using Java and C++ together
The HotSpot JVM is itself C++: its style guide says it was “originally written in a subset of the C++98/03 language”, and C++17 is now supported. Java calls native code through JNI or, since Java 22, the Foreign Function and Memory API (JEP 454). JNI calls functions written for it, each taking a JNIEnv* and declared extern "C" in C++ unless registered with RegisterNatives; the FFM API calls C functions through the platform’s ABI, so the C++ side exposes extern "C" functions with C types. No sample here ran the two together.
Where Java and C++ are used
- TIOBE index, November 2026: Java third (8.89%), C++ fourth (8.35%).
- Stack Overflow Developer Survey 2026 (n = 14,099), extensive development work in the past year: Java 27.6%, C++ 24.3%.
- Written in C++: the HotSpot JVM and LLVM (“standard C++17” unless documented otherwise); Unreal Engine provides “a robust framework for C++ programmers”.
- Written in Java: Hadoop (GitHub counts its repository as 91% Java, November 2026), Elasticsearch (“built using Java”, in Elastic’s install guide) and Kafka (Java and Scala).
Java or C++: which to choose, or learn first
| Situation | Choose | Why |
|---|---|---|
| A game engine, a compiler, a database engine | C++ | you decide layout, copies and when memory is freed |
| Firmware, or hard limits on memory or pause times | C++ | no collector, no JVM |
| A web service or a large codebase on the JVM | Java | defined failures, a collector, the JVM’s libraries and tools |
| Code where a bad index or a null must throw | Java | every access is checked; C++ checks only through at(), a hardened library or a sanitizer |
| A first language for a job | the one the job uses | either teaches the other’s syntax; neither teaches its habits |
Stroustrup’s FAQ puts it in one line: “Despite the syntactic similarities, C++ and Java are very different languages.” More answers for working C++ developers are in Careers.
Questions
What is the main difference between Java and C++?
C++ compiles to machine code, keeps objects as values destroyed at a known point, and leaves mistakes such as signed overflow or an index past the end undefined. Java compiles to bytecode that a JVM runs, reaches objects through references (a garbage collector frees unreachable ones), and gives those mistakes a defined result: a wrap or an exception. In code, the first difference you meet is assignment: it copies an object in C++ and copies a reference in Java.
Is C++ faster than Java?
Nothing was timed here. On the Benchmarks Game (a quad-core i5-3330; C++ with g++ 14.2 in January 2025, Java 23 in February 2025), the fastest C++ programs took 2.15 s for n-body, 3.33 s for fannkuch-redux and 0.72 s for spectral-norm, against Java’s 6.92 s, 10.48 s and 1.61 s. The site flags those C++ programs for possible hand-written vector instructions, “unsafe” or naked ffi; its fastest unflagged C++ programs took 5.15 s, 7.86 s and 1.53 s.
Is C++ harder than Java?
C++ leaves more to you. In the samples, signed overflow and an index past the end compiled without a warning on the four C++ compilers tested, and both are undefined behavior, while Java wrapped the integer and threw on the index; a local read before it was set compiled in C++ and was a compile error in Java. C++ also leaves ownership, copying and memory layout to your code, where Java decides them for you.
Should I learn Java or C++ first?
Learn the one the job or the course you want uses. Either teaches the other’s syntax, and neither teaches the other’s habits, such as what assignment and == do with objects. Stroustrup’s FAQ puts the gap in one line: “Despite the syntactic similarities, C++ and Java are very different languages.”
Is Java written in C++?
Partly. The HotSpot JVM in OpenJDK is C++: its style guide says HotSpot was “originally written in a subset of the C++98/03 language” and that C++17 is now supported. The javac compiler and the class library are written in Java: on 11 November 2026, GitHub counted the openjdk/jdk repository as 73.9% Java, 14.5% C++ and 7.7% C.
Can Java call C++ code?
Yes, through functions with C types. Java calls native code through the Java Native Interface (JNI), whose native methods take a JNIEnv pointer and are declared with C language linkage in C++, or, since Java 22, through the Foreign Function and Memory API (JEP 454), which calls C functions, so a C++ library is exposed through functions with C linkage.
Sources
- JLS SE 25, Chapter 4: Types, Values, and VariablesOracle
- JLS SE 25, Chapter 15: ExpressionsOracle
- [expr.pre] Expressions: preambleC++ standard working draft
- JEP 454: Foreign Function & Memory APIOpenJDK
- HotSpot Coding StyleOpenJDK
- C++ g++ versus Java fastest programsThe Computer Language Benchmarks Game
- TIOBE IndexTIOBE
- Bjarne Stroustrup’s FAQBjarne Stroustrup