Java is always pass-by-value, for both primitive types and non-primitive types.
e.g.,
void JavaMethod(int i, Foo f)
{
i = 9; //not changed from caller's point of view
f = new Foo(); //not changed from caller's point of view
}
C# is pass-by-value unless the 'ref' keyword is used:
void CSharpMethod(int i, Foo f, ref int j, ref Bar b)
{
i = 9; //not changed from caller's point of view
f = new Foo(); //not changed from caller's point of view
j = 9; //changed from caller's point of view
b = new Bar(); //changed from caller's point of view
}
In both languages, you can modify the internal state of an non-primitive object when it is passed by value:
void JavaOrCSharpMethod(Foo f)
{
f.field = 9; //internal state is changed from caller's point of view
}
Keep in mind the difference between assigning a new instance to a parameter and modifying the internal state of the object. Not understanding this is the source of a lot of confusion about this subject.
Also, there is no real substance in whether a primitive type or non-primitive type is used, other than the fact that primitive types do not have members which change their state, so it's always done via assignment. Any non-primitive type where state can only be changed via assignment would seem the same.
I would include some C++ examples also, but this would drastically complicate the discussion since C++ has many ways of implementing pass-by-reference.