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
  1. 1Java vs C++ at a glance
  2. 2Values and references: what = and == do
  3. 3Memory: a destructor or a garbage collector
  4. 4What a mistake does: undefined in C++, defined in Java
  5. 5C++ templates and Java generics
  6. 6What else differs between Java and C++
  7. 7Is C++ faster than Java?
  8. 8Using Java and C++ together
  9. 9Where Java and C++ are used
  10. 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.

Two sheets with the same five lines of code-like strokes. The left rests directly on a wide hatched block; the right stands on a magenta layer laid over that block, so it sits higher.
Figure 1 — The two languages read alike and stand on different machinery.

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.

Left: reading.cpp passes through a compiler to machine code, run directly by the processor. Right: Reading.java passes through javac to bytecode, run by a JVM (interpreter, JIT compiler, garbage collector) on the processor. A dotted line marks where running begins.
Figure 2 — C++ is compiled to machine code before it runs; Java is compiled to bytecode, which a JVM interprets and compiles to machine code while it runs.

The same program in both, five readings and the total of those above a limit, reads almost line for line:

[Example 1:
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]

[Example 2:
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:

[Example 3:
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:

[Example 4:
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]

[Example 5:
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:

Top: a std::vector of Reading, one block of three cells side by side, each holding an id and a value. Bottom: an ArrayList of Reading, three small cells, each with an arrow to a separate id-and-value box, the boxes scattered.
Figure 3 — A C++ vector holds the objects themselves, side by side; a Java list holds references to objects elsewhere on the heap.

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"):

[Example 6:
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:

[Example 7:
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:

[Example 8:
    ~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]

[Example 9:
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:

[Example 10:
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 ArrayIndexOutOfBoundsException (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):

[Example 11:
int total = std::numeric_limits<int>::max();
std::println("{}", total + 1);   // undefined: no value to report

— end example]

[Example 12:
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:

[Example 13:
-2147483648
Exception in thread "main" java.lang.ArithmeticException: integer overflow

— end example]

Two tools make the overflow visible in C++:

  • Built with -fsanitize=signed-integer-overflow, GCC 16.2 and Clang 23.1 reported 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 constexpr function call (Clang: value 2147483648 is outside the range of representable values of type 'int'); written directly as constexpr 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::_M_range_check: __n (which is 3) >= this->size() (which is 3) -D_GLIBCXX_ASSERTIONS Assertion '__n < this->size()' failed., then SIGABRT
libc++ vector -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST 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.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3; ArrayList.get(3) threw IndexOutOfBoundsException, the array exception’s superclass.

For a null, JDK 27 names the call that returned it:

[Example 14:
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::bad_optional_access 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):

[Example 15:
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:

[Example 16:
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:

[Example 17:
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:

[Example 18:
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:

[Example 19:
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:

[Example 20:
List<Integer> limits = List.of(20, 25);
limits.add(30);                 // compiles; throws when it runs

— end example]

It threw:

[Example 21:
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.

Read next

  1. Clang vs GCC: what differs for a C++ projectTooling · Tobias Mwangi · written for C++23
  2. C++ generators: co_yield and std::generatorConcurrency · Miriam Castellanos · written for C++23
  3. C++ vs C: what differs, what breaks, which to chooseCareers · Tobias Mwangi · written for C++23