C:\1990S\JAVA.TXT
1995
Java: The Program Traveled With a Contract
Java's 1995 announcement proposed a portable execution environment, with bytecode separating the distributed program from the machine that carried it out.
A program calculates postage. Its arithmetic is unremarkable, its interface is small, and somebody would like it to work on a different computer. The request sounds reasonable until the list of differences arrives.
The processor may use different instructions. The operating system may provide different services. A library may be missing. Even a simple calculation can depend on assumptions the author never thought to write down.
Java made those assumptions part of a platform design. A program could be compiled into an intermediate form, then executed by an implementation of the Java virtual machine on the destination system. The program traveled with a defined set of expectations; each platform supplied machinery that honored them.
That is the interesting promise underneath portability. Work doesn’t vanish when a program moves. Some of it moves into the runtime, where it can be shared by many programs.
The announcement and the platform
Sun announced Java and the HotJava browser on May 23, 1995. A company bulletin also announced Netscape’s intention to license Java for Navigator. The intention and the language announcement aren’t proof that final Java support was already installed in every browser.1
The language had earlier been called Oak. Its designers’ preserved 1996 preface describes James Gosling’s embedded consumer-electronics project and contributions by Ed Frank, Patrick Naughton, Jonathan Payne and Chris Warth before the Internet retargeting. It credits a larger group in shaping the language’s final form.2
The shift makes sense as an engineering problem. A program delivered over a network may arrive at a machine its author never inspected. Tying distribution to a particular processor and operating-system combination narrows where that program can go.
Public announcement also needs separating from a finished development kit. The Java tutorial’s January 23, 1996 changelog says its links were updated for the new final JDK 1.0 release. That establishes availability by then, rather than putting final JDK 1.0 in May 1995.3
The explanation here uses the early platform architecture described in Gosling and Henry McGilton’s May 1996 white paper. It isn’t an inventory of every feature in a particular 1995 alpha build. Swing, generics and later Java platforms belong to later stories.
An intermediate instruction set
The white paper describes a compiler producing architecture-neutral bytecode and platform-specific virtual-machine implementations executing it. Defined primitive sizes and arithmetic support consistent behavior. It also describes automatic memory management and the absence of C-style pointer arithmetic.4
Those choices put a contract between application and host. The compiler targets the contract. The runtime implements it using the actual machine’s facilities.
A virtual machine in this sense is an abstract execution model. It doesn’t need to pretend to be a complete physical PC with a particular disk controller and display card. The relevant question is whether it implements the operations and services the program expects.
Original explanatory diagram, 2026. Simplified platform relationship, not a screenshot, historical compiler output or a diagram of every Java subsystem.
Let’s make the arithmetic concrete with an invented miniature. We want a postage calculation that adds a base charge to an extra charge. The inputs are 120 and 35, expressed in an invented smallest currency unit so we can use integers.
The desired result is 155. No currency conversion, real postal tariff or rounding policy is implied.
Instead of reproducing JVM instruction bytes, we’ll use explicitly invented symbolic instructions for a small stack model:
LOAD_ARG 0
LOAD_ARG 1
ADD32
RETURN
These labels aren’t actual JVM mnemonics or recovered compiler output. They let us examine the execution contract without suggesting that this article has run an original JDK.
Follow the calculation
A stack holds values with a defined top. Our model places operands there and removes them when an operation consumes them.
At entry, the arguments are available separately: argument 0 is 120 and argument 1 is 35. The operand stack starts empty.
| Step | Operation | Stack afterward |
|---|---|---|
| 1 | Load argument 0 | [120] |
| 2 | Load argument 1 | [120, 35] |
| 3 | Add the top two values as signed 32-bit integers | [155] |
| 4 | Return the top value | [], result 155 |
In step 3, the top value is 35, with 120 beneath it. The operation removes both and pushes their sum. Addition happens to be commutative, but instruction semantics still need to specify operand order for operations where order matters.
The stack is an explanatory way to track the state. It doesn’t establish that a runtime must physically store every intermediate operand in an array exactly like the table. An implementation can organize its internal machinery differently while preserving the required observable result.
Now imagine two hosts with different processors. Host A’s runtime and Host B’s runtime both receive our instruction sequence and arguments. Each must produce 155 if it honors the model’s rules.
They may use different registers, instructions and internal representations. The program’s contract supplies the common meaning; the native operations supply the actual execution.
This explains why an intermediate instruction isn’t a promise that a processor directly understands it. Without the appropriate runtime, a byte sequence is just a byte sequence. Portability requires someone to build and maintain the destination side of the arrangement.
Our checker uses two independently written evaluation paths: one follows the supplied stack program, while another directly evaluates the integer operation. It compares their answers on ordinary and boundary inputs. These are teaching evaluators on modern Python, not two real Java implementations or CPU ports.
Arithmetic is part of the agreement
Portability becomes more interesting when the answer exceeds the range of a data type. “An integer” isn’t a complete description of the rules.
Our ADD32 operation uses a signed 32-bit result. There are possible bit patterns. We explicitly define wraparound by keeping the low 32 bits, then interpreting the top bit as the sign.
Take the largest positive signed value, 2,147,483,647, and add 1. The mathematical sum is 2,147,483,648. Under the model’s signed 32-bit rule, the result is −2,147,483,648.
Another example is −1 plus 2, which gives 1. A third adds two largest positive values; the low-bit result is −2. Our checker includes these cases.
The point isn’t that wraparound is desirable for postage. A service should validate the range its business calculation requires. The point is that two hosts need the same answer to the same operation, even when the programmer would have preferred a different answer.
A platform contract can make an error consistent. It doesn’t make the calculation sensible.
This is also why “same source” and “same behavior” aren’t automatically equivalent in every language or environment. An unspecified size or operation leaves room for implementation differences. Specifying it turns an assumption into something an implementation can be tested against.
Original teaching diagram, 2026. Invented symbolic instructions and argument values; not actual JVM disassembly or a 1995 execution trace.
The rest of the program also travels
Our miniature has no files, interface, threads or network services. That makes its portable contract conveniently small. A complete application usually asks for much more.
Imagine adding a command that opens a local configuration file at a hardcoded location. The integer addition can remain consistent while the application fails because the file isn’t present. The virtual machine can’t supply a user’s missing data merely by knowing how to add.
Or imagine adding a native library with operations supplied only for one processor. The portable portion of the application hasn’t become meaningless, but deployment now has another dependency. Moving the intermediate code alone doesn’t fulfill that dependency.
An interface can introduce its own expectations about fonts, screen space or interaction. A network operation depends on a reachable service and on how failure is handled. Timing can differ even when arithmetic and types agree.
These examples don’t refute portability. They describe its scope. A well-defined intermediate execution model removes a substantial class of differences while leaving other conditions to document and manage.
I find that scope more useful than the strongest possible reading of a slogan. A platform can offer a common foundation without promising that every program ignores the outside world. A postage program still needs a tariff, somewhere to obtain its inputs and a meaningful response when those inputs are unavailable.
A runtime takes on responsibilities
Automatic memory management changes another part of the division of labor. The runtime can reclaim objects no longer needed according to its reachability rules, rather than requiring the programmer to explicitly free each allocation.4
That transfers responsibility; it doesn’t create unlimited memory. Keep references to objects that an application no longer uses, and the runtime may still have reasons to retain them. A service can exhaust resources while following the language’s basic rules.
The network setting raises a further question. If a program arrives from elsewhere, executing it gives its instructions an opportunity to act on the machine. Controlling that opportunity requires rules about permissions and access, alongside checks on the program’s structure.
The early white paper presents security as a major design goal. Its confident claims shouldn’t be read as proof that a particular runtime could never contain a vulnerability.4 A design goal establishes what the engineers were trying to accomplish; implementation quality determines how well the boundary holds.
Our teaching interpreter deliberately has almost no powers. It can load two values, add and return. That narrow scope makes its behavior easy to inspect. Adding file access would require another explicit operation and another policy decision. Adding arbitrary host calls would enlarge the contract much more dramatically.
The general lesson is practical: a runtime’s capabilities and restrictions are part of the program’s environment. Calling code portable doesn’t answer how much authority it should receive.
A shared foundation has maintenance costs
Java’s early platform proposal placed a reusable layer beneath many applications. Developers could target its defined operations, while runtime implementers handled the host-specific work.
That arrangement concentrates effort and responsibility. A fix to a runtime can benefit many programs; an incompatibility can affect many programs too. The layer has to be maintained as carefully as the applications that rely on it.
The postage example makes the promise small enough to inspect. Two different hosts can agree that 120 plus 35 is 155 because their implementations agree on what the intermediate operation means. Boundary values make the agreement more demanding, and external dependencies make it larger still.
The program can travel. Its expectations need somewhere reliable to land.
Footnotes
-
SunFlash, May 1995 company bulletin, entries 77.25 and 77.29, dated May 23: Java/HotJava announcement and Netscape licensing intention. ↩
-
Gosling, Joy and Steele, preserved July 1996 preface, retained within the third-edition site. Used for earlier Oak origins and contributor roles, not later language features. ↩
-
Sun/Oracle, Java tutorial history, January 23, 1996 entry linking the new final JDK 1.0 release. Establishes availability by that date. ↩
-
James Gosling and Henry McGilton, The Java Language Environment, introduction, contents and May 1996 date. Selected architecture, specified arithmetic and memory-management principles; original symbolic interpreter isn’t JVM bytecode or a historical runtime test. ↩ ↩2 ↩3