Reasons why Java sucks compared to C#

Reason C# Java
Generics can't hold primitive types e.g. int.
List<int>
ArrayList<Integer>
You can't use square brackets (indexers) to get and set elements in anything other than arrays.
intList[i] = 3;
dict["hi"] += 4;
intList.set(i, 3);
dict.set("hi", dict.get("hi") + 4);
Streams are more cumbersome than Linq.
string.Join(", ",
	ints.Where(i => i % 2 == 0)
);
ints.stream()
	.filter(i -> i % 2 == 0)
	.mapToObj(Integer::toString)
	.collect(Collectors.joining(", "));
Arrays don't implement the List interface.
void PrintElements(IList<int> lst) {
	// lst is an array/List/etc.
	foreach (int x in lst) {
		Console.WriteLine(x);
	}
}
void printElements(List<Integer> lst) {
	// lst can't be an array.
	for (int x : lst) {
		System.out.println(x);
	}
}

void printElements(Integer[] lst) {
	for (int x : lst) {
		System.out.println(x);
	}
}
No primitive unsigned byte type. Seriously, what? How often are signed bytes useful?
byte[] bs = GetSomeBytes();

foreach (byte b in bs) {
	Console.WriteLine(
		"Unsigned byte: " + b);
}
byte[] bs = getSomeBytes();

for (byte b : bs) {
	int ub = (int) b & 0xff;
	System.out.println(
		"Unsigned byte: " + ub);
}
No property support.
public double Hours
{
	get { return seconds / 3600; }
	set { seconds = value * 3600; }
}
public double getHours() {
	return seconds / 3600;
}

public void setHours(double hours) {
	seconds = hours * 3600;
}
No overflow checking.
// (Enable overflow checking in
// compiler options.)

int a = int.MaxValue
// Throws OverflowException.
a = a + 1
int a = Integer.MAX_VALUE;
// Assigns -2147483648 to a.
a = a + 1;
No reference/output parameters.
int num;
if (int.TryParse("2", out num)) {
	Console.WriteLine(num);
}
Integer num = null;
try {
	num = Integer.parseInt("2");
} catch (NumberFormatException ex) {
}

if (num != null) {
	System.out.println(num);
}
No optional arguments.
void PrintNum(int i, string message = "Num") {
	Console.WriteLine($"{message}: {i}");
}
void printNum(int i, String message) {
	System.out.printf("%s: %d\n", message, i);
}

void printNum(int i) {
	printNum(i, "Num");
}
No operator overloading.
public class Main {
	public static void Main(string[] args) {
		Vector v1 = new Vector(1, 2);
		Vector v2 = new Vector(2, 1);

		Vector v3 = v1 + v2;
	}
}

public class Vector {
	//...

	public static Vector operator +(
			Vector v1, Vector v2) {
		return new Vector(v1.X + v2.X, v1.Y + v2.Y);
	}
}
public class Main {
	public static void main(String[] args) {
		Vector v1 = new Vector(1, 2);
		Vector v2 = new Vector(2, 1);

		Vector v3 = v1.add(v2);
	}
}

public class Vector {
	//...

	public Vector add(
			Vector v1, Vector v2) {
		return new Vector(v1.x + v2.x, v1.y + v2.y);
	}
}