Java Syntax Legends: System.out
By Shimi Bandiel, Solution Architect @ JFrog
January 5, 2022
4 min read
There is a story I want to share with you today that underscores an important principle about seemingly inconsequential decisions. It shows how small design decisions can one day have a huge impact and one possible outcome of technical debt. I’m sure all of you Java programmers out there are well familiar with System.out.
It was there since the inception of Java and has been used heavily since day one. At inception, the System.out, System.in, System.err were regular mutable fields (you could redirect the standard input/output/error). Shortly after its inception, one of the popular use-cases for Java was Applets (good riddance) that were used for rich web applications.
Java introduced a sandbox model, SecurityManager, that allowed those Applets to safely be executed on clients’ machines essentially preventing any dangerous operation from taking place.
Some of those “dangerous operations” were assignments to System.out, System.err, and System.in.
In order for the SecurityManager to check the operation, a method to invoke was required. Hence came System.setOut(), System.setErr(), System.setIn(). In these methods, the SecurityManager would check for authorized access. However, and this is the design issue, the Java authors didn’t encapsulate the field itself! They still provided access directly to the field for reading (they didn’t introduce getOut(), getErr(), getIn()).
Maybe that was due to backward compatibility, or because getOut() is a bit too much. In order to prevent direct modification to the fields, they made the field final and in the setter methods, used Reflection to modify the field.
Doesn’t sound too bad, right?
About 6 years went by (versions 1.2, 1.3 and 1.4)
Then came version 5. Finally Java got a proper memory model (JSR 133).
Now, in the specification, they finally enforced that final members are thread-safe and you don’t have to synchronize access to them.
Makes sense, right?
Wait, what about the final members: System.in/out/err?
They are final, however, they are not safe (due to mutation by Reflection) and they are part of the standard library. What can be done?
If you go through the Java specification and read the part about the Java Memory Model, you’ll see that those three fields are exempted from the thread-safety guarantee of final fields:
“Normally, a field that is final and static may not be modified. However, System.in, System.out, and System.err are static final fields that, for legacy reasons, must be allowed to be changed by the methods System.setIn, System.setOut, and System.setErr. We refer to these fields as being write-protected to distinguish them from ordinary final fields.”
In short, when accessing these fields, you should use a synchronization mechanism.
Now, not only are most Java developers unaware of this fact, but also bugs might be introduced that are extremely hard to track down. For example, you might reference System.out and the value will not be refreshed after another thread redirects it to a file.
The moral of the story is that a small technical debt/bad design decision (not encapsulating a field properly) might (several years later) result in an exemption in the actual language specification. Exemptions like this can lead to unexpected behavior!