I'm guessing this mostly happens during serialization to and from strings.
One programmer does String.valueOf(x) instead of x.toString() to prevent NullPointerExceptions.
This works pretty well until the next guy does x == null || x.equals("null") because "null"s pop up in UI.
At this point this is irreversible as nobody can tell what is null and what is "null".
It doesn't have to be at the language level. It's not unreasonable to assume that someone, somewhere in the mists of time decided that the string "NULL" was a reasonable choice for representing missing data in some input data and things kind of snowballed from there.
I've seen this in code I've reviewed before, where people will check for `== "empty"` instead of just using types like `null` which were designed for this (in javascript).
A practise that may come from some shell scripting languages where `[ $x == "" ]` is an invalid command because empty strings aren't strings.
That may be true in older shells, but in modern shells [ $x == "" ] is valid, and "" is a single token whose value is the empty string. But I've seen old code that does things like
if [ x$x == "x" ] ; then ...
apparently because it was necessary at one time.
It depends on what you mean by "older" shells. More modern shells tend to support this properly, but the baseline for most shell scripts is /bin/sh which is not guaranteed to be modern – it is often a new release of Sh.
Anyone who's done even a trivial amount of programming in the shell knows to do: [ "$x" == "" ]
That does not help, as far as POSIX is concerned.
$ [ "$x" == "" ]
sh: 2: [: unexpected operator
Normally through coercion, and where the idea of a missing field hasn't been considered by a parser.
Until something gets stored as CSV at some point where there's no straight-forward way to store Null.
An empty field?
No, an empty field represents "" - the empty string. NULL and "" are not the same thing.