This article compares two programming languages: C# with Java. While the focus of this article is mainly the languages and their features, such a comparison will necessarily also consider some features of platforms and libraries.

C# and Java are similar languages that are typed statically, strongly, and manifestly. Both are object-oriented, and designed with semi-interpretation or runtime just-in-time compilation, and both are curly brace languages, like C and C++.

Types Data types Java C# Arbitrary-size decimals Arbitrary-size integers Arrays Boolean type Character Complex numbers Date/time ; reference type ; value type Enumerated types ; reference type ; scalar High-precision decimal number ; but see 'Arbitrary-size decimals' above IEEE 754 binary32 floating point number IEEE 754 binary64 floating point number Lifted (nullable) types ; but wrapper types Pointers ; Java avoids pointers to ensure memory safety and automatic garbage collection Only available in unsafe mode or through System.IntPtr Reference types Signed integers ; 8, 16, 32, 64 bits ; 8, 16, 32, 64 bits Strings Type annotations Single-root (unified) type system The type system is unified by default, unless the compiler is switched into unsafe mode where raw pointers are available as a datatype. Pointers are not derived from object nor does it have implicit conversions to/from the object data type Tuples ; Records and libraries provide similar functionality Unsigned integers ; Operations supported via library methods ; 8, 16, 32, 64 bits Value types ; primitive types only

Unified type system Both languages are statically typed with class-based object orientation. In Java the primitive types are special in that they are not object-oriented and they could not have been defined using the language itself. They also do not share a common ancestor with reference types. The Java reference types all derive from a common root type. C# has a unified type system in which all types (besides unsafe pointers) ultimately derive from a common root type. Consequently, all types implement the methods of this root type, and extension methods defined for the type apply to all types, even primitive literals and delegates. This allows C#, unlike Java, to support objects with encapsulation that are not reference types.

In Java, compound types are synonymous with reference types; methods cannot be defined for a type unless it is also a class reference type. In C# the concepts of encapsulation and methods have been decoupled from the reference requirement so that a type can support methods and encapsulation without being a reference type. Only reference types support virtual methods and specialization, however.

Both languages support many built-in types that are copied and passed by value rather than by reference. Java calls these types primitive types, while they are called simple types in C#. The primitive/simple types typically have native support from the underlying processor architecture.

The C# simple types implement several interfaces and consequently offer many methods directly on instances of the types, even on the literals. The C# type names are also merely aliases for Common Language Runtime (CLR) types. The C# type is exactly the same type as the type; the only difference is that the former is the canonical .NET name, while the latter is a C# alias for it.

Java does not offer methods directly on primitive types. Instead, methods that operate on primitive values are offered through companion primitive wrapper classes. A fixed set of such wrapper classes exist, each of which wraps one of the fixed set of primitive types. As an example, the Java type is a reference type that wraps the primitive type. They are not the same type, however.

Data types Numeric types Signed integers Both Java and C# support signed integers with bit widths of 8, 16, 32 and 64 bits. They use the same name/aliases for the types, except for the 8-bit integer that is called a in Java and a (signed byte) in C#.

Unsigned integers C# supports unsigned in addition to the signed integer types. The unsigned types are , , and for 8, 16, 32 and 64 bit widths, respectively. Unsigned arithmetic operating on the types are supported as well. For example, adding two unsigned integers (s) still yields a as a result; not a long or signed integer.

Java does not feature unsigned integer types. In particular, Java lacks a primitive type for an unsigned byte. Instead, Java's type is sign extended, which is a common source of bugs and confusion.

Unsigned integers were left out of Java deliberately because James Gosling believed that programmers would not understand how unsigned arithmetic works.In programming language design, one of the standard problems is that the language grows so complex that nobody can understand it. One of the little experiments I tried was asking people about the rules for unsigned arithmetic in C. It turns out nobody understands how unsigned arithmetic in C works. There are a few obvious things that people understand, but many people don't understand it.

Java versions 8 and 9 added some limited built-in unsigned integer operations, but they are only exposed as static methods on the primitive wrapper classes; they operate on signed primitive integer types, treating them as if they were unsigned.

High-precision decimal numbers C# has a type and literal notation for high-precision (28 decimal digits) decimal arithmetic that is appropriate for financial and monetary calculations. Contrary to the and data types, decimal fractional numbers such as 0.1 can be represented exactly in the decimal representation. In the float and double representations, such numbers often have non-terminating binary expansions, making those representations more prone to round-off errors.

While Java lacks such a built-in type, the Java library does feature an arbitrary precision decimal type. This is not considered a language type and it does not support the usual arithmetic operators; rather it is a reference type that must be manipulated using the type methods. See more about arbitrary-size/precision numbers below.

Advanced numeric types Both languages offer library-defined arbitrary-precision arithmetic types for arbitrary-size integers and decimal point calculations.

Only Java has a data type for arbitrary precision decimal point calculations. Only C# has a type for working with complex numbers.

In both languages, the number of operations that can be performed on the advanced numeric types is limited compared to the built-in IEEE 754 floating point types. For instance, none of the arbitrary-size types support square root or logarithms.

C# allows library-defined types to be integrated with existing types and operators by using custom implicit/explicit conversions and operator overloading. See example in section Integration of library-defined types

Characters Both languages feature a native (character) datatype as a simple type. Although the type can be used with bit-wise operators, this is performed by promoting the value to an integer value before the operation. Thus, the result of a bitwise operation is a numeric type, not a character, in both languages.

Built-in compound data types Both languages treat strings as (immutable) objects of reference type. In both languages, the type contains several methods to manipulate strings, parse, format, etc. In both languages regular expressions are considered an external feature and are implemented in separate classes.

Both languages' libraries define classes for working with dates, times, time zones, and calendars in different cultures. Java provides , a mutable reference type with millisecond precision, and (since Java 8) the package (including classes such as , , and for date-only, time-only, and date-and-time values), a set of immutable reference types with nanosecond precision. In contrast, the C# is an immutable struct value type for date-and-time information with 100-nanosecond precision; the .NET 6 API also added and , similar structures for date-only or time-only operations. C# also defines a type for working with time periods; Java 8 provides the class for the same purpose. Both languages support date and time arithmetic according to different cultures and time zones.

User-defined value type (struct) C# allows creating user-defined value types, using the keyword. Unlike classes and like the standard primitives, such value types are passed and assigned by value rather than by reference. They can also be part of an object (either as a field or boxed), or stored in an array without the memory indirection that normally exists for class types.

Because value types have no notion of a value and can be used in arrays without initialization, they always come with an implicit default constructor that essentially fills the struct memory space with zeroes. Further constructors can be defined only with one or more arguments. Value types do not have virtual method tables, and because of that (and the fixed memory footprint), they are implicitly sealed. However, value types can (and frequently do) implement interfaces. For example, the built-in integer types implement several interfaces.

Apart from the built-in primitive types, Java does not include the concept of value types.

Enumerations Both languages define enumerations, but they are implemented in fundamentally different ways. As such, enumerations are one area where tools designed to automatically translate code between the two languages (such as Java to C# converters) fail.

C# has implemented enumerations in a manner similar to C, that is as wrappers around the bit-flags implemented in primitive integral types (int, byte, short, etc.). This has performance benefits and improves interaction with C/C++ compiled code, but provides fewer features and can lead to bugs if low-level value types are directly cast to an enumeration type, as is allowed in the C# language. Therefore, it is seen as syntactic sugar. In contrast, Java implements enumerations as full featured collection of instances, requiring more memory and not aiding interaction with C/C++ code, but providing added features in reflection and intrinsic behavior. The implementation in each language is described in the table below.

Java C# Definition In Java, the enumeration type is a class, and its values are objects (instances) of that class. Enums are all implicitly derived from . The only valid values are the ones listed in the enumeration. The enumeration type may declare fields, allowing each individual enumerated value to reference further data associated uniquely with that specific value. The enumeration type may also declare or override methods, or implement interfaces. Enumerations in C# are implicitly derived from the type that again is a value type derivative. The value set of a C# enumeration is defined by the underlying type that can be a signed or unsigned integer type of 8, 16, 32 or 64 bits. The enumeration definition defines names for the selected integer values. By default the first name is assigned the value 0 (zero) and the following names are assigned in increments of 1. Any value of the underlying primitive type is a valid value of the enumeration type, though an explicit cast may be needed to assign it. Combining Java enumeration set and map collections provide functionality to combine multiple enumeration values to a combined value. These special collections allow compiler optimization to minimize the overhead incurred by using collections as the combination mechanism. C# supports bit-mapped enumerations where an actual value may be a combination of enumerated values bitwise or'ed together. The formatting and parsing methods implicitly defined by the type will attempt to use these values.