Java Applets: Adding Real Interactivity to Web Pages (1999)
Sun Microsystems launched Java in 1995 with a specific promise: "Write Once, Run Anywhere." For web development, this meant Java Applets - compiled Java programs that ran inside the browser via a Java Virtual Machine plugin. The pitch was compelling: full graphics, animation, sound, networking, and user interaction, all inside a <applet> tag on an ordinary HTML page.
In 1999, if you needed genuine interactive graphics - a real-time chart, a draggable diagram, an animated visualization - Java Applets were the only serious option. JavaScript was limited to form validation and simple DOM manipulation. Flash existed but was proprietary and expensive. Java was open, standardized, and backed by Sun.
We built applets. Here is what that looked like.
The Architecture: JDK 1.1 and AWT
Java Applets used the Abstract Window Toolkit (AWT) for graphics and UI. The model was event-driven: the browser called your applet's lifecycle methods (init(), start(), stop(), destroy()), and you overrode paint() to draw to the screen.
// StockTickerApplet.java - JDK 1.1, circa 1999
// A scrolling stock ticker - one of the classic 1999 applet use cases
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
public class StockTickerApplet extends Applet implements Runnable {
// Stock data passed from the HTML page via <param> tags
private String tickerText = "MSFT 89.50 (+1.25) AAPL 28.75 (-0.50) SUNW 42.00 (+2.00) IBM 134.25 (+0.75)";
private int scrollSpeed = 2; // pixels per frame
private Color bgColor = Color.black;
private Color textColor = Color.green; // green-on-black: very 1999
private Thread animationThread;
private int xPosition;
private int textWidth;
private Font tickerFont;
@Override
public void init() {
// Read parameters from <param> tags in HTML
String paramText = getParameter("text");
String paramSpeed = getParameter("speed");
if (paramText != null) tickerText = paramText;
if (paramSpeed != null) scrollSpeed = Integer.parseInt(paramSpeed);
tickerFont = new Font("Monospaced", Font.BOLD, 14);
// Set applet background
setBackground(bgColor);
// Measure how wide the text will be
FontMetrics fm = getFontMetrics(tickerFont);
textWidth = fm.stringWidth(tickerText);
xPosition = getSize().width; // start off screen to the right
}
@Override
public void start() {
// Called when the browser tab becomes visible
if (animationThread == null || !animationThread.isAlive()) {
animationThread = new Thread(this);
animationThread.start();
}
}
@Override
public void stop() {
// Called when the user navigates away - stop wasting CPU
if (animationThread != null) {
animationThread = null;
}
}
@Override
public void run() {
// Animation loop - runs in its own thread
while (Thread.currentThread() == animationThread) {
xPosition -= scrollSpeed;
// Reset when text has scrolled fully off screen
if (xPosition < -textWidth) {
xPosition = getSize().width;
}
repaint(); // triggers paint()
try {
Thread.sleep(40); // ~25 frames per second
} catch (InterruptedException e) {
break;
}
}
}
@Override
public void paint(Graphics g) {
// AWT Graphics - the 1999 way to draw
g.setFont(tickerFont);
g.setColor(textColor);
g.drawString(tickerText, xPosition, getSize().height / 2 + 5);
}
// Double-buffering to eliminate flicker - mandatory in 1999 AWT
// Without this, every repaint() causes a visible flash
private Image offscreenImage;
private Graphics offscreenGraphics;
@Override
public void update(Graphics g) {
Dimension d = getSize();
if (offscreenImage == null) {
offscreenImage = createImage(d.width, d.height);
offscreenGraphics = offscreenImage.getGraphics();
}
// Draw everything off-screen first
offscreenGraphics.setColor(bgColor);
offscreenGraphics.fillRect(0, 0, d.width, d.height);
paint(offscreenGraphics);
// Then blit to screen in one operation
g.drawImage(offscreenImage, 0, 0, this);
}
}
Compile and deploy:
# JDK 1.1 compilation
javac StockTickerApplet.java
# This produces: StockTickerApplet.class
# Bundle into a JAR for faster download (one HTTP request instead of one per class):
jar cvf ticker.jar StockTickerApplet.class
Embed in HTML:
<!-- HTML 4.0 applet tag - deprecated in HTML 4.01 but universally used -->
<applet code="StockTickerApplet.class"
archive="ticker.jar"
width="500"
height="30"
alt="Stock ticker requires Java">
<param name="text" value="MSFT 89.50 (+1.25) AAPL 28.75 (-0.50) IBM 134.25 (+0.75)">
<param name="speed" value="3">
<!-- Fallback for browsers without Java plugin -->
<p>Your browser does not support Java applets.
<a href="ticker.html">View static version</a></p>
</applet>
A More Complex Example: Interactive Chart
// BarChartApplet.java - interactive bar chart, JDK 1.1 / AWT
// Bars highlight on mouseover - something impossible with 1999 JavaScript
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
public class BarChartApplet extends Applet implements MouseMotionListener {
private String[] labels = {"Jan", "Feb", "Mar", "Apr", "May", "Jun"};
private int[] values = {42, 67, 55, 89, 73, 91};
private int hoveredBar = -1;
@Override
public void init() {
addMouseMotionListener(this);
setBackground(Color.white);
// Parse values from <param> tags
String paramValues = getParameter("values");
if (paramValues != null) {
String[] parts = paramValues.split(",");
values = new int[parts.length];
for (int i = 0; i < parts.length; i++) {
values[i] = Integer.parseInt(parts[i].trim());
}
}
}
@Override
public void paint(Graphics g) {
int w = getSize().width;
int h = getSize().height;
int margin = 30;
int barW = (w - 2 * margin) / values.length - 4;
int maxVal = 0;
for (int v : values) if (v > maxVal) maxVal = v;
// Draw axes
g.setColor(Color.black);
g.drawLine(margin, h - margin, w - margin, h - margin); // X axis
g.drawLine(margin, margin, margin, h - margin); // Y axis
// Draw bars
for (int i = 0; i < values.length; i++) {
int barH = (int)((double)values[i] / maxVal * (h - 2 * margin));
int x = margin + i * (barW + 4) + 4;
int y = h - margin - barH;
// Highlight hovered bar
g.setColor(i == hoveredBar ? Color.orange : new Color(51, 102, 204));
g.fillRect(x, y, barW, barH);
g.setColor(Color.black);
g.drawRect(x, y, barW, barH);
// Label
g.setFont(new Font("SansSerif", Font.PLAIN, 10));
g.drawString(labels[i < labels.length ? i : 0], x, h - 15);
// Value above bar
g.drawString(String.valueOf(values[i]), x, y - 2);
}
}
@Override
public void mouseMoved(MouseEvent e) {
int w = getSize().width;
int h = getSize().height;
int margin = 30;
int barW = (w - 2 * margin) / values.length - 4;
int newHovered = -1;
for (int i = 0; i < values.length; i++) {
int x = margin + i * (barW + 4) + 4;
if (e.getX() >= x && e.getX() <= x + barW) {
newHovered = i;
break;
}
}
if (newHovered != hoveredBar) {
hoveredBar = newHovered;
repaint();
}
}
@Override public void mouseDragged(MouseEvent e) {}
}
This was genuinely impressive in 1999. A bar chart that highlighted on mouseover - in JavaScript you could not do this at all. The best you could do with JavaScript was swap <img> tags for rollover effects. Java let you write real interactive graphics code.
The Problems That Killed Applets
Startup time. The JVM plugin had to load before any applet code ran. On a 56k modem, users stared at a grey box for 10-30 seconds waiting for the JVM to initialize. Many users clicked away before seeing anything.
Version fragmentation. IE shipped its own Microsoft JVM. Netscape used the Sun JVM plugin. Java 1.0, 1.1, and 1.2 had incompatible APIs. An applet that worked perfectly in IE/Windows would fail in Netscape/Mac or vice versa. The "Write Once, Run Anywhere" promise broke down on different browser/OS combinations constantly.
// Code that exposed JVM version differences:
// java.awt.Color constructor behavior differed between JDK 1.0 and 1.1
// Netscape's JVM implementation of Thread.sleep() was buggy in some versions
// Microsoft's JVM omitted some RMI classes entirely
// We added version detection:
String javaVersion = System.getProperty("java.version");
// Then branched on the version - defeating the entire WORA premise
Security restrictions. Applets ran in a sandbox. They could not read local files, write to the filesystem, or connect to servers other than the one they were loaded from. This was correct for security, but it meant applets could not be used as general-purpose applications without complex workarounds (signed applets, security policy files).
Flash was shinier. Macromedia Flash 4 (1999) and Flash 5 (2000) offered vector animation, streaming audio, and ActionScript programming with a dramatically better authoring experience. Flash loaded faster due to its binary format, required no JVM, and let designers (not just Java programmers) create interactive content. By 2001, Flash had won the "interactive web" category.
What Java Applets Established
Despite failing as a mainstream web technology, Java Applets demonstrated that the browser could be a platform for real application development - not just document display. This idea persisted and evolved: Flash, then Silverlight, then HTML5 Canvas, WebGL, and WebAssembly all pursued the same goal of running real programs in the browser.
The architectural patterns of applet development - lifecycle methods (init/start/stop/destroy), event listeners, double-buffered rendering - appear in modified form in every UI framework that followed. Android's Activity lifecycle is a direct descendant of the Java Applet lifecycle. The browser component model that React and Web Components embody follows the same init/render/destroy pattern.
The failure was instructive: "Write Once, Run Anywhere" requires not just language portability but runtime portability. The JVM was not identical across browser vendors and operating systems. The lesson - that the runtime environment must be standardized, not just the language - drove the eventual standardization of JavaScript engines and the decision to build the modern web platform around a single, agreed-upon runtime.
Aunimeda builds production-grade backend systems - APIs, microservices, real-time applications, and system integrations.
Contact us for backend engineering services. See also: Custom Software Development, Web Development