The problem in languages like Java, C#, etc..is that best practice would be to wrap up the string in a class, but that's a "heavyweight" solution and developers are lazy. But these "stringy" bugs can be nasty when the wrong string is passed into the wrong argument.
In some languages you can at least "typedef" or "type alias" strings so that in your type declarations and argument types, it's the typedef and not just a raw string that is being passed in.
e.g. (made up pseudocode)
typedef ZipCode as string;
State LookupState(ZipCode zip);
So with solutions that don't involve wrapping everything in a class, you can incrementally develop your type by first doing the "typedef" and then adding functions.
So that's the problem with languages like Java. You develop a "I don't need a whole class for this" attitude because you can't incrementally develop your type safety.
In some languages you can at least "typedef" or "type alias" strings so that in your type declarations and argument types, it's the typedef and not just a raw string that is being passed in.
e.g. (made up pseudocode)
typedef ZipCode as string; State LookupState(ZipCode zip);
So with solutions that don't involve wrapping everything in a class, you can incrementally develop your type by first doing the "typedef" and then adding functions.
So that's the problem with languages like Java. You develop a "I don't need a whole class for this" attitude because you can't incrementally develop your type safety.