Cover image
Back-end
10 minute read

An In-depth Look at C++ vs. Java

Deciding on a language can be intimidating when you don’t have deep experience with the available options. This comparison explores the fundamental differences between C++ and Java, and what to consider when choosing between them.

Countless articles compare C++ and Java’s technical features, but which differences are most important to consider? When a comparison shows, for example, that Java doesn’t support multiple inheritance and C++ does, what does that mean? And is it a good thing? Some argue that this is an advantage of Java, while others declare it a problem.

Let’s explore the situations in which developers should choose C++, Java, or another language altogether—and, even more importantly, why the decision matters.

Examining the Basics: Language Builds and Ecosystems

C++ launched in 1985 as a front end to C compilers, similar to how TypeScript compiles to JavaScript. Modern C++ compilers typically compile to native machine code. Though some claim C++’s compilers reduce its portability, and they do necessitate rebuilds for new target architectures, C++ code runs on almost every processor platform.

First released in 1995, Java doesn’t build directly to native code. Instead, Java builds bytecode, an intermediate binary representation that runs on the Java Virtual Machine (JVM). In other words, the Java compiler’s output needs a platform-specific native executable to run.

Both C++ and Java fall into the family of C-like languages, as they generally resemble C in their syntax. The most significant difference is their ecosystems: While C++ can seamlessly call into libraries based on C or C++, or the API of an operating system, Java is best suited for Java-based libraries. You can access C libraries in Java using the Java Native Interface (JNI) API, but it is error-prone and requires some C or C++ code. C++ also interacts with hardware more easily than Java, as C++ is a lower-level language.

Detailed Trade-offs: Generics, Memory, and More

We can compare C++ to Java from many perspectives. In some cases, the decision between C++ and Java is clear. Native Android applications should typically use Java unless the app is a game. Most game developers should opt for C++ or another language for the smoothest possible real-time animation; Java’s memory management often causes lag during gameplay.

Cross-platform applications that aren’t games are beyond the scope of this discussion. Neither C++ nor Java are ideal in this case because they’re too verbose for efficient GUI development. For high-performance apps, it’s best to create C++ modules to do the heavy lifting, and use a more developer-productive language for the GUI.

Cross-platform applications that aren’t games are beyond the scope of this discussion. Neither C++ nor Java are ideal in this case because they’re too verbose for efficient GUI development.

For some projects, the choice may not be clear, so let’s compare further:

Feature C++ Java
Beginner-friendly No Yes
Runtime performance Best Good
Latency Predictable Unpredictable
Reference-counting smart pointers Yes No
Global mark-and-sweep garbage collection No Required
Stack memory allocation Yes No
Compilation to native executable Yes No
Compilation to Java bytecode No Yes
Direct interaction with low-level operating system APIs Yes Requires C code
Direct interaction with C libraries Yes Requires C code
Direct interaction with Java libraries Through JNI Yes
Standardized build and package management No Maven


Aside from the features compared in the table, we’ll also focus on object-oriented programming (OOP) features like multiple inheritance, generics/templates, and reflection. Note that both languages support OOP: Java mandates it, while C++ supports OOP alongside global functions and static data.

Multiple Inheritance

In OOP, inheritance is when a child class inherits attributes and methods from a parent class. One standard example is a Rectangle class that inherits from a more generic Shape class:

// Note that we are in a C++ file
class Shape {
    // Position
    int x, y;
  public:
    // The child class must override this pure virtual function
    virtual void draw() = 0;
};
 
class Rectangle: public Shape {
    // Width and height
    int w, h;
  public:
    void draw();
};

Multiple inheritance is when a child class inherits from multiple parents. Here’s an example, using the Rectangle and Shape classes and an additional Clickable class:

// Not recommended
class Shape {...};
class Rectangle: public Shape {...};
 
class Clickable {
    int xClick, yClick;
  public:
    virtual void click() = 0;
};
 
class ClickableRectangle: public Rectangle, public Clickable {
    void click();
};

In this case we have two base types: Shape (the base type of Rectangle) and Clickable. ClickableRectangle inherits from both to compose the two object types.

C++ supports multiple inheritance; Java does not. Multiple inheritance is useful in certain edge cases, such as:

  • Creating an advanced domain-specific language (DSL).
  • Performing sophisticated calculations at compile time.
  • Improving project type safety in ways that are simply not possible in Java.

However, using multiple inheritance is generally discouraged. It can complicate code and impact performance unless combined with template metaprogramming—something best done by only the most experienced C++ programmers.

Generics and Templates

Generic versions of classes that work with any data type are practical for code reuse. Both languages offer this support—Java through generics, C++ through templates—but the flexibility of C++ templates can make advanced programming safer and more robust. C++ compilers create new customized classes or functions each time you use different types with the template. Moreover, C++ templates can call custom functions based on the types of the parameters of the top-level function, allowing particular data types to have specialized code. This is called template specialization. Java doesn’t have an equivalent feature.

In contrast, when using generics, Java compilers create general objects without types through a process called type erasure. Java performs type-checking during compilation, but programmers cannot modify the behavior of a generic class or method based on its type parameters. To understand this better, let’s look at a quick example of a generic std::string format(std::string fmt, T1 item1, T2 item2) function that uses a template, template<class T1, class T2>, from a C++ library I created:

std::string firstParameter = "A string";
int secondParameter = 123;
// Format printed output as an eight-character-wide string and a hexadecimal value
format("%8s %x", firstParameter, secondParameter);
// Format printed output as two eight-character-wide strings
format("%8s %8s", firstParameter, secondParameter);

C++ would produce the format function as std::string format(std::string fmt, std::string item1, int item2), whereas Java would create it without the specific string and int object types for item1 and item2. In this case, our C++ template knows the last incoming parameter is an int and therefore can perform the necessary std::to_string conversion in the second format call. Without templates, a C++ printf statement trying to print a number as a string as in the second format call would have undefined behavior and could crash the application or print garbage. The Java function would only be able to treat a number as a string in the first format call and would not format it as a hexadecimal integer directly. This is a trivial example, but it demonstrates C++’s ability to select a specialized template to handle any arbitrary class object without modifying its class or the format function. We can produce the output correctly in Java using reflection instead of generics, though this method is less extensible and more error-prone.

Reflection

In Java, it is possible to find out (at runtime) structural details like which members are available in a class or class type. This feature is called reflection, presumably because it’s like holding up a mirror to the object to see what’s inside. (More information can be found in Oracle’s reflection documentation.)

C++ doesn’t have full reflection, but modern C++ offers runtime type information (RTTI). RTTI allows for runtime detection of specific object types, though it cannot access information like the object’s members.

Memory Management

Another critical difference between C++ and Java is memory management, which has two major approaches: manual, where developers must keep track of and release memory manually; and automatic, where software tracks which objects are still in use to recycle unused memory. In Java, an example is garbage collection.

Java requires garbage-collected memory, providing easier memory management than the manual approach and eliminating memory-releasing errors that commonly contribute to security vulnerabilities. C++ doesn’t provide automatic memory management natively, but it does support a form of garbage collection called smart pointers. Smart pointers use reference counting and are secure and performant if used correctly. C++ also offers destructors that clean up or release resources upon an object’s destruction.

While Java only offers heap allocation, C++ supports both heap allocation (using new and delete or the older C malloc functions) and stack allocation. Stack allocation can be faster and safer than heap allocation because a stack is a linear data structure while a heap is tree-based, so stack memory is much simpler to allocate and release.

Another advantage of C++ related to stack allocation is a programming technique known as Resource Acquisition Is Initialization (RAII). In RAII, resources such as references tie to the life cycle of their controlling object; the resources will be destroyed at the end of that object’s life cycle. RAII is how C++ smart pointers work without manual dereferencing—a smart pointer referenced at the top of a function is automatically dereferenced upon exiting the function. The connected memory is also released if this is the last reference to the smart pointer. Though Java offers a similar pattern, it’s more awkward than C++’s RAII, especially if you need to create several resources in the same code block.

Runtime Performance

Java has solid runtime performance, but C++ still holds the crown since manual memory management is faster than garbage collection for real-world applications. Though Java can outperform C++ in certain corner cases due to JIT compilation, C++ wins most non-trivial cases.

In particular, Java’s standard memory library overworks the garbage collector with its allocations compared to C++’s reduced use of heap allocations. However, Java is still relatively fast and should be acceptable unless latency is a top concern—for example, in games or applications with real-time constraints.

Build and Package Management

What Java lacks in performance, it makes up for in ease of use. One component affecting developer efficiency is build and package management—how we build projects and bring external dependencies into an application. In Java, a tool called Maven simplifies this process into a few easy steps and integrates with many IDEs such as IntelliJ IDEA.

In C++, however, no standardized package repository exists. There isn’t even a standardized method to build C++ code in applications: Some developers prefer Visual Studio, while others use CMake or another custom set of tools. Further adding to the complexity, certain commercial C++ libraries are binary-formatted, and there is no consistent way to integrate those libraries into the build process. Moreover, variations in build settings or compiler versions can cause challenges in getting binary libraries to work.

Beginner-friendliness

Build and package management friction isn’t the only reason C++ is far less beginner-friendly than Java. A programmer may have difficulty debugging and using C++ safely unless they’re familiar with C, assembly languages, or a computer’s lower-level workings. Think of C++ like a power tool: It can accomplish a lot but it is dangerous if misused.

Java’s aforementioned memory management approach also makes it much more accessible than C++. Java programmers do not have to worry about releasing object memory since the language takes care of that automatically.

Decision Time: C++ or Java?

A flowchart with a dark blue "Start" bubble in the top-left corner that eventually connects to one of seven light-blue conclusion boxes beneath it, via a series of white decision junctions with dark blue branches for "Yes" and other options, and light blue branches for "No." The first is "Cross-platform GUI app?" from which a "Yes" points to the conclusion, "Pick a cross-platform development environment and use its primary language." A "No" points to "Native Android App?" from which a "Yes" points to a secondary question, "Is it a game?" From the secondary question, a "No" points to the conclusion, "Use Java (or Kotlin)," and a "Yes" points to a different conclusion, "Pick a cross-platform game engine and use its recommended language." From the "Native Android App?" question, a "No" points to "Native Windows app?" from which a "Yes" points to a secondary question, "Is it a game?" From the secondary question, a "Yes" points to the conclusion, "Pick a cross-platform game engine and use its recommended language," and a "No" points to a different conclusion, "Pick a Windows GUI environment and use its primary language (typically C++ or C#)." From the "Native Windows app?" question, a "No" points to "Server app?" from which a "Yes" points to a secondary question, "Developer type?" From the secondary question, a "Mid-skill" decision points to the conclusion, "Use Java (or C# or TypeScript)," and a "Skilled" decision points to a tertiary question, "Top priority?" From the tertiary question, a "Developer productivity" decision points to the conclusion, "Use Java (or C# or TypeScript)," and a "Performance" decision points to a different conclusion, "Use C++ (or Rust)." From the "Server app?" question, a "No" points to a secondary question, "Driver development?" From the secondary question, a "Yes" points to a conclusion, "Use C++ (or Rust)," and a "No" points to a tertiary question, "IoT development?" From the tertiary question, "Yes" points to the conclusion, "Use C++ (or Rust)," and a "No" points to a quaternary question, "High-speed trading?" From the quaternary question, a "Yes" points to the conclusion, "Use C++ (or Rust)," and a "No" points to the final remaining conclusion, "Ask someone familiar with your target domain."
An expanded guide to choosing the best language for various project types.

Now that we’ve explored the differences between C++ and Java in-depth, we return to our original question: C++ or Java? Even with a deep understanding of the two languages, there is no one-size-fits-all answer.

Software engineers unfamiliar with low-level programming concepts might be better off selecting Java when restricting the decision to either C++ or Java, except for real-time contexts like gaming. Developers looking to expand their horizons, on the other hand, might learn more by choosing C++.

However, the technical differences between C++ and Java may only be a small factor in the decision. Certain types of products require particular choices. If you still aren’t sure, you can consult the flowchart—but keep in mind that it may ultimately point you to a third language.

Understanding the basics

C++ generally outperforms Java and has access to lower-level features that Java lacks, but each language has its advantages.

Java and C++ have different strengths. Deciding which is more useful for your project depends on your goals; for example, if beginner-friendliness is a top priority, Java is the optimal choice.

Think of it as the difference between two cities. In some cities, it’s easier to bike but harder to use the subway. Instructions on how to get from one place to another will vary. General differences between C++ and Java include compiler outputs, compatible libraries, and productivity features.

Both C++ and Java are C-family languages, meaning that their syntaxes have elements in common with C. The initial design of Java was modeled after C++, which explains their many similarities.

Comments

Kingstone Job
I studied C before venture into C++ and Java. But eventually I found Java to be beginner friendly than C++ plus with many Java Communities and Development Tools compared to C++ and frankly I still consider Java as a language which support multiple inheritance through the use of interface. And I think inheriting from lots of class can put you at a disadvantage as well. Build Management like Maven and Cradle in Java enhances productivity as well. Perhaps the greatest advantage of C++ over Java is performance.
TimMensch
C++ is <i>huge</i>. It also is pointlessly complicated in many cases do to relatively simple things. From the documentation, the recommended way to upper-case a string is: <code> std::transform(s.cbegin(), s.cend(), s.begin(), [](unsigned char c) { return std::toupper(c); }); </code> Is that code really beginner-friendly? Compare to most other modern languages where the same operation is some variant of: <code>s.toUpper();</code> C++ is also a power tool that has few protections against crashing your app if you don't know what you're doing. In my experience, even C++11 is too big for a beginner to absorb more than the very basics in less than about two years. I wouldn't want anyone writing C++ code on a project without in-depth code review until they had at least three solid years of experience. And C++ has had multiple major releases that added features since I was last using it extensively, so it's even bigger now. A beginner who is finding it easy to do basic things has likely simply not hit the many land mines that are hidden deep in the language--and they're probably doing a ton of things in a suboptimal manner. Any beginner who thinks C++ is easy is using some subset of C++, and any starting to use any code written in a <i>different</i> subset of C++ is going to be difficult or dangerous for the developer. Think of it this way: The true advantage of C++ is its speed and control. If a developer isn't achieving the speed because they don't understand what's going on well enough, and they don't understand enough to be writing extremely low level code and therefore don't need the control, then what's left? Java gets you close to the level of speed and adds a layer of safety on top. TypeScript gets you a type system more sophisticated than the one in C++*, leading to safer code, and provides an amazing boost to developer productivity compared to either Java or C++. And developer productivity in this case translates directly into "ease of use for a beginner." So instead of needing to understand how pointers really work (even smart pointers have their traps), and instead of needing to write a ton of extra binding and conversion code for most every operation, you're writing less code and needing to understand less. So that's what I mean by it's not friendly to beginners: I'm comparing it to languages that are <i>more</i> friendly to beginners. * Note: C++ templates can do calculations at compile time and TypeScript generics can't, but overall the power of the type system in TypeScript is stronger, with built-in nullable type support and full type algebra. Not to mention structural typing.
comments powered by Disqus