Chapter 08: Graphics

Chapter 08: Graphics

January 9, 2026 | Previous part |

Java has a rich history of libraries for creating Graphical User Interfaces (GUIs) and working with graphics. From the original Abstract Window Toolkit (AWT) to the robust Swing library, and finally to the modern JavaFX platform, Java provides multiple ways to interact with users beyond the command line.

In this chapter, we will explore the evolution of these tools. We will start with the basics of image manipulation with AWT, move to building windows with Swing, and conclude with the modern JavaFX platform and web architectures.

A Brief History of Java GUIs

To understand why there are so many ways to build a window in Java, it helps to know the history.

1. AWT (Abstract Window Toolkit) - 1995

When Java 1.0 was released, it included AWT. AWT used the operating system’s native components (“peers”) to draw buttons and windows.

  • Pros: Looked exactly like the native OS.
  • Cons: “Write Once, Run Anywhere” was hard because different OSs behaved differently. It was “heavyweight” and limited to the lowest common denominator of features.

2. Swing (JFC) - 1998

Swing was built on top of AWT but rendered components itself using Java code. These were called “lightweight” components.

  • Pros: Consistent look and feel across all platforms. Much richer set of components (Tables, Trees, Tabbed Panes).
  • Cons: Could be slower than native apps. The default look (“Metal”) was often criticized.
  • Status: Still widely used in enterprise legacy apps and simple tools (like the ones we build in class).

3. JavaFX - 2008

JavaFX was designed to replace Swing. It uses hardware acceleration (GPU) for modern graphics, supports CSS for styling, and FXML for layout.

  • Pros: Modern look, 3D support, animation support, separates design from logic.
  • Cons: Not included in the JDK by default anymore (requires separate download/setup).
  • Status: The recommended choice for new desktop applications.

4. The Web Era (Spring Boot) - Present

Today, many developers use Java for the backend (server) and build the frontend using Web technologies (HTML/JavaScript/React). However, for desktop tools, scientific applications, and internal enterprise software, JavaFX and Swing remain powerful choices.

What You Will Learn

  • AWT Graphics: How to load, modify, and save images using BufferedImage and Graphics2D.
  • Swing Components: How to build windows using JFrame, JPanel, JButton, and JTextField.
  • Advanced Swing: Using Menus, File Choosers, and Timers for animation.
  • Dependency Management: How to use Maven or Gradle to manage external libraries like JavaFX.
  • Modern GUIs with JavaFX: The basics of the JavaFX architecture (Stage, Scene, Nodes) and CSS styling.
  • FXML: Separating your UI design from your Java code.
  • Java and the Web: Understanding how Java backends (Spring Boot) interact with modern JavaScript frontends.

Working with Images and Graphics

Java provides powerful tools for manipulating images and graphics through the java.awt (Abstract Window Toolkit) package. Even without building a GUI, we can write programs that process image files, draw shapes, and create art.

Coming Attractions

Before we dive into the code, let’s meet the stars of the show. These are the classes you will use most often when working with 2D graphics in Java.

  • BufferedImage: The canvas. It represents an image stored in memory that you can modify.
  • ImageIO: The loader/saver. It handles reading images from disk (JPG, PNG, GIF) and writing them back out.
  • Graphics / Graphics2D: The artist. This object provides methods to draw lines, rectangles, ovals, and text onto a BufferedImage.
  • Color: The palette. It represents colors using Red, Green, and Blue (RGB) components.
  • Font: The typography. It defines the style, size, and family of the text you draw.

1. The Canvas: BufferedImage

The core class for handling images in memory is java.awt.image.BufferedImage. It represents an image with an accessible buffer of image data.

To read and write images, we use javax.imageio.ImageIO.

import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;

public class ImageLoader {
    void main() throws Exception {
        File file = new File("image.jpg");
        BufferedImage image = ImageIO.read(file);
        
        System.out.println("Width: " + image.getWidth());
        System.out.println("Height: " + image.getHeight());
    }
}

2. The Artist: Graphics2D

While you can manipulate pixels directly (as we’ll see later), it’s often easier to “draw” on an image. You can get a Graphics2D object from any BufferedImage.

BufferedImage image = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();

// Now we can draw!
g.setColor(Color.RED);
g.fillRect(0, 0, 500, 500); // Fill background with red

Drawing Shapes

The Graphics class has methods for all standard shapes:

  • drawLine(x1, y1, x2, y2)
  • drawRect(x, y, width, height) / fillRect(...)
  • drawOval(x, y, width, height) / fillOval(...)
  • drawPolygon(xPoints, yPoints, nPoints)

Drawing Text

You can also draw text directly onto an image. This is how you might create a meme generator or add watermarks.

g.setFont(new Font("Arial", Font.BOLD, 48));
g.setColor(Color.WHITE);
g.drawString("Hello Java!", 50, 100);

3. The Palette: Color

The java.awt.Color class is used to set the current drawing color.

Built-in Colors

Java provides constants for standard colors: Color.RED, Color.BLUE, Color.GREEN, Color.BLACK, Color.WHITE, etc.

Custom Colors

You can create any color by specifying its Red, Green, and Blue (RGB) components (0-255).

Color myPurple = new Color(128, 0, 128);
g.setColor(myPurple);

Transparency (Alpha)

You can also add a fourth parameter for “Alpha” (transparency), where 0 is invisible and 255 is solid.

Color semiTransparentBlue = new Color(0, 0, 255, 128); // 50% see-through

4. Pixel Manipulation

Sometimes drawing shapes isn’t enough. You want to change the image pixel by pixel. We can access the color of any pixel using getRGB(x, y) and change it using setRGB(x, y, color).

Example: Inverting Colors

Here is a program that reads an image, inverts every pixel’s color (like a photo negative), and saves the result.

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Scanner;
import javax.imageio.ImageIO;

public class ImageInverter {
    void main() throws Exception {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter the path to an image file: ");
        String filePath = scanner.nextLine();
        
        File selectedFile = new File(filePath);
        if (!selectedFile.exists()) {
            System.out.println("File not found!");
            return;
        }

        BufferedImage image = ImageIO.read(selectedFile);
        
        // Process the image
        invertColors(image);
        
        // Save the new image
        File outputFile = new File("inverted-image.jpg");
        ImageIO.write(image, "jpg", outputFile);
        System.out.println("Saved to " + outputFile.getAbsolutePath());
    }

    void invertColors(BufferedImage image) {
        int w = image.getWidth();
        int h = image.getHeight();

        for (int x = 0; x < w; x++) {
            for (int y = 0; y < h; y++) {
                // Get current color
                Color c = new Color(image.getRGB(x, y));
                
                // Invert components
                int newRed = 255 - c.getRed();
                int newGreen = 255 - c.getGreen();
                int newBlue = 255 - c.getBlue();
                
                // Set new color
                Color newColor = new Color(newRed, newGreen, newBlue);
                image.setRGB(x, y, newColor.getRGB());
            }
        }
    }
}

Project: The Meme Generator

Let’s combine Graphics2D, Font, and ImageIO to create a simple meme generator. This program will load an image and write text at the top and bottom.

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;

public class MemeGenerator {
    void main() throws Exception {
        // 1. Load the image
        BufferedImage image = ImageIO.read(new File("cat.jpg"));
        
        // 2. Get the Graphics object
        Graphics2D g = image.createGraphics();
        
        // 3. Enable Anti-aliasing for smooth text
        g.setRenderingHint(
            RenderingHints.KEY_TEXT_ANTIALIASING, 
            RenderingHints.VALUE_TEXT_ANTIALIAS_ON
        );

        // 4. Setup Font
        int fontSize = image.getWidth() / 10; // Scale font to image width
        Font font = new Font("Impact", Font.BOLD, fontSize);
        g.setFont(font);
        
        // 5. Draw Text (Top)
        String topText = "I CAN HAS";
        drawCenteredString(g, topText, image.getWidth(), fontSize);
        
        // 6. Draw Text (Bottom)
        String bottomText = "JAVA?";
        drawCenteredString(g, bottomText, image.getWidth(), image.getHeight() - 20);
        
        // 7. Clean up and Save
        g.dispose();
        ImageIO.write(image, "jpg", new File("meme.jpg"));
    }

    void drawCenteredString(Graphics2D g, String text, int width, int y) {
        // Calculate x to center the text
        FontMetrics metrics = g.getFontMetrics();
        int x = (width - metrics.stringWidth(text)) / 2;
        
        // Draw Outline (Black)
        g.setColor(Color.BLACK);
        g.drawString(text, x+2, y+2);
        
        // Draw Text (White)
        g.setColor(Color.WHITE);
        g.drawString(text, x, y);
    }
}

This example demonstrates how powerful the standard library is. With just a few lines of code, we can perform complex image manipulation tasks.

Swing Components and Event Handling

While JOptionPane provides a quick way to pop up messages, most graphical applications require a persistent window with interactive elements like buttons, text fields, and labels. In Java’s Swing library, we build these interfaces using Components and Containers.

The Hierarchy of Swing

Swing components are organized in a hierarchy.

  1. Top-Level Containers: The main windows of your application. The most common is JFrame.
  2. Intermediate Containers: Panels that hold other components to organize the layout. The most common is JPanel.
  3. Atomic Components: The individual elements the user interacts with, such as JButton, JLabel, and JTextField.

1. The Main Window: JFrame

Every Swing application starts with a JFrame. It represents a window with a title bar, border, and buttons to minimize, maximize, or close.

import javax.swing.JFrame;

public class SimpleWindow {
    void main() {
        // 1. Create the frame
        JFrame frame = new JFrame("My First Window");

        // 2. Set the size (width, height)
        frame.setSize(400, 300);

        // 3. Define what happens when the user clicks the 'X'
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // 4. Make it visible
        frame.setVisible(true);
    }
}
  • setDefaultCloseOperation: By default, closing a window just hides it. EXIT_ON_CLOSE ensures the program actually stops running.

2. Adding Components

You rarely add components directly to a JFrame. Instead, you add them to the frame’s Content Pane, or more commonly, you create a JPanel, add components to that panel, and then add the panel to the frame.

Common Components

  • JLabel: Displays read-only text or images.
  • JButton: A clickable button that triggers an action.
  • JTextField: A single-line input box for user text.
import javax.swing.*;

public class ComponentDemo {
    void main() {
        JFrame frame = new JFrame("Component Demo");
        frame.setSize(300, 200);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Create a panel to hold our components
        JPanel panel = new JPanel();

        // Create components
        JLabel label = new JLabel("Enter your name:");
        JTextField nameField = new JTextField(15); // 15 columns wide
        JButton button = new JButton("Say Hello");

        // Add components to the panel
        panel.add(label);
        panel.add(nameField);
        panel.add(button);

        // Add the panel to the frame
        frame.add(panel);

        frame.setVisible(true);
    }
}

3. Layout Managers

You might notice in the example above that the components just flow in a line. This is because JPanel uses a Layout Manager to decide where to put things.

Java provides several layout managers to control how components are arranged.

FlowLayout (Default for JPanel)

Arranges components in a line, wrapping to the next line if the window is too narrow. It centers them by default.

BorderLayout (Default for JFrame)

Divides the container into five regions: North, South, East, West, and Center.

JPanel panel = new JPanel(new BorderLayout());
panel.add(new JButton("North"), BorderLayout.NORTH);
panel.add(new JButton("South"), BorderLayout.SOUTH);
panel.add(new JButton("Center"), BorderLayout.CENTER);

GridLayout

Arranges components in a grid of cells of equal size.

// 3 rows, 2 columns
JPanel panel = new JPanel(new GridLayout(3, 2)); 
panel.add(new JButton("1"));
panel.add(new JButton("2"));
// ... and so on

4. Event Handling: Making it Interactive

A GUI isn’t useful if it doesn’t do anything. We need to handle Events, such as a button click.

In Swing, we use an ActionListener. When a button is clicked, it fires an ActionEvent. We register a listener to “listen” for that event and run code when it happens.

The Old Way (Anonymous Inner Classes)

Before Java 8, this was verbose:

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button clicked!");
    }
});

The Modern Way (Lambdas)

Since ActionListener is a functional interface (it has only one method), we can use a Lambda expression.

button.addActionListener(e -> {
    System.out.println("Button clicked!");
});

Putting It All Together: The Greeter App

Let’s build a complete application. This app will have a label, a text field for the user’s name, and a button. When the button is clicked, it will update a message label.

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;

public class GreeterApp {
    
    // We declare components as fields so we can access them inside methods
    private JLabel messageLabel;
    private JTextField nameField;

    void main() {
        // 1. Setup the Frame
        JFrame frame = new JFrame("Greeter App");
        frame.setSize(400, 150);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        // 2. Setup the Layout
        // We'll use a BorderLayout for the main structure
        frame.setLayout(new BorderLayout());

        // 3. Create Top Panel (Input)
        JPanel topPanel = new JPanel(); // Uses FlowLayout by default
        topPanel.add(new JLabel("Name: "));
        
        nameField = new JTextField(15);
        topPanel.add(nameField);
        
        JButton greetButton = new JButton("Greet Me!");
        // Add the event listener
        greetButton.addActionListener(e -> updateMessage());
        
        topPanel.add(greetButton);

        // 4. Create Center Panel (Output)
        JPanel centerPanel = new JPanel();
        messageLabel = new JLabel("Enter a name above.");
        messageLabel.setFont(new Font("Arial", Font.BOLD, 18));
        messageLabel.setForeground(Color.BLUE);
        centerPanel.add(messageLabel);

        // 5. Add panels to the frame
        frame.add(topPanel, BorderLayout.NORTH);
        frame.add(centerPanel, BorderLayout.CENTER);

        // 6. Show it
        frame.setVisible(true);
    }

    // Helper method called when button is clicked
    private void updateMessage() {
        String name = nameField.getText();
        if (name.isBlank()) {
            messageLabel.setText("Please enter a name!");
            messageLabel.setForeground(Color.RED);
        } else {
            messageLabel.setText("Hello, " + name + "!");
            messageLabel.setForeground(Color.BLUE);
        }
    }
}

Key Takeaways

  1. Separation of Concerns: We separated the layout code (in main) from the logic code (updateMessage).
  2. Accessing State: We used nameField.getText() to read input and messageLabel.setText() to change the UI.
  3. Styling: We used setFont and setForeground to make the text look better.

Simple Dialog Boxes (Recap)

As mentioned in the introduction, sometimes you don’t need a full window. For simple alerts, JOptionPane is still your best friend.

  • JOptionPane.showMessageDialog(parent, message): Show info.
  • JOptionPane.showInputDialog(message): Get a string.
  • JOptionPane.showConfirmDialog(parent, message): Get a Yes/No/Cancel response.

Swing is a massive library, but these building blocks—Frames, Panels, Components, Layouts, and Listeners—are the foundation of everything you will build with it.

Advanced Swing: Menus, Files, and Timers

Once you have mastered the basics of Frames, Panels, and Buttons, you can start adding more professional features to your Swing applications. In this section, we will look at adding Menu Bars, opening files, and creating simple animations.

1. The Menu Bar

Most desktop applications have a menu bar at the top of the window (File, Edit, View, etc.). In Swing, this is built using three components:

  1. JMenuBar: The bar itself that sits at the top of the frame.
  2. JMenu: The dropdown categories (e.g., “File”).
  3. JMenuItem: The individual clickable options (e.g., “Open”, “Save”, “Exit”).
import javax.swing.*;
import java.awt.event.ActionEvent;

public class MenuDemo {
    void main() {
        JFrame frame = new JFrame("Menu Demo");
        frame.setSize(400, 300);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // 1. Create the Menu Bar
        JMenuBar menuBar = new JMenuBar();

        // 2. Create a Menu
        JMenu fileMenu = new JMenu("File");
        JMenu helpMenu = new JMenu("Help");

        // 3. Create Menu Items
        JMenuItem openItem = new JMenuItem("Open");
        JMenuItem exitItem = new JMenuItem("Exit");
        
        // 4. Add Actions
        exitItem.addActionListener(e -> System.exit(0));
        
        openItem.addActionListener(e -> {
            JOptionPane.showMessageDialog(frame, "Open clicked!");
        });

        // 5. Assemble them
        fileMenu.add(openItem);
        fileMenu.addSeparator(); // Adds a line between items
        fileMenu.add(exitItem);

        menuBar.add(fileMenu);
        menuBar.add(helpMenu);

        // 6. Set the Menu Bar on the Frame
        frame.setJMenuBar(menuBar);

        frame.setVisible(true);
    }
}

2. Opening Files with JFileChooser

We often need to let users select a file from their computer. The JFileChooser component provides a standard dialog for navigating the file system.

JFileChooser fileChooser = new JFileChooser();

// Optional: Set the starting directory to the user's home folder
fileChooser.setCurrentDirectory(new java.io.File(System.getProperty("user.home")));

// Show the "Open" dialog
int result = fileChooser.showOpenDialog(frame);

if (result == JFileChooser.APPROVE_OPTION) {
    // User selected a file
    java.io.File selectedFile = fileChooser.getSelectedFile();
    System.out.println("Selected file: " + selectedFile.getAbsolutePath());
} else {
    System.out.println("User cancelled the operation");
}

You can also use showSaveDialog(parent) if you want to save a file.

3. Simple Animation with javax.swing.Timer

Swing is single-threaded. If you write a while(true) loop in your main method to move an object, your GUI will freeze because the “Event Dispatch Thread” (EDT) is blocked.

To perform repetitive tasks (like animation) without freezing the UI, use javax.swing.Timer.

Note: Be careful not to import java.util.Timer, which is a different class.

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class BouncingBall {
    int x = 0;
    int y = 100;
    int xSpeed = 5;

    void main() {
        JFrame frame = new JFrame("Animation");
        frame.setSize(400, 300);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Custom panel for drawing
        JPanel panel = new JPanel() {
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g); // Clear the screen
                g.setColor(Color.RED);
                g.fillOval(x, y, 30, 30); // Draw the ball
            }
        };

        frame.add(panel);
        frame.setVisible(true);

        // Create a Timer that fires every 30 milliseconds (~33 FPS)
        Timer timer = new Timer(30, e -> {
            // Update logic
            x += xSpeed;
            
            // Bounce off walls
            if (x > panel.getWidth() - 30 || x < 0) {
                xSpeed = -xSpeed;
            }

            // Trigger a repaint
            panel.repaint();
        });

        timer.start();
    }
}

How it Works

  1. Timer(delay, listener): Creates a timer that calls the listener’s actionPerformed method every delay milliseconds.
  2. panel.repaint(): Tells Swing that the component needs to be redrawn. Swing will call paintComponent as soon as possible.
  3. paintComponent(Graphics g): This is where we do our custom drawing. Always call super.paintComponent(g) first to ensure the background is cleared.

By combining Timer, paintComponent, and event listeners, you can build simple games like Pong or Snake entirely in Swing!

Setting Up JavaFX with Build Tools

Unlike Swing, JavaFX is not included in the standard JDK (since Java 11). This means you cannot just import javafx.* and run your code; you must download the JavaFX library and tell your Java environment where to find it.

While you can manually download the SDK and configure your classpath (as shown in the “Manual Setup” section below), the professional way to manage dependencies in Java is using a Build Tool.

What is a Build Tool?

A build tool automates the process of compiling your code, managing libraries (dependencies), and packaging your application. The two most popular build tools in the Java ecosystem are Maven and Gradle.

Instead of downloading .jar files and manually adding them to your project, you simply list the libraries you need in a configuration file. The build tool downloads them from a central repository (like Maven Central) and ensures they are available to your program.

Option 1: Using Maven

Maven uses an XML file called pom.xml (Project Object Model) to describe your project.

1. Create a Maven Project

In VS Code, you can create a new Maven project using the “Create Java Project” command and selecting “Maven”.

2. The pom.xml File

To use JavaFX, you add the dependencies to your pom.xml.

<dependencies>
    <!-- JavaFX Controls -->
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-controls</artifactId>
        <version>21</version>
    </dependency>
    <!-- JavaFX FXML (if using FXML layouts) -->
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-fxml</artifactId>
        <version>21</version>
    </dependency>
</dependencies>

3. The JavaFX Plugin

To run your application easily, you should also add the JavaFX Maven Plugin.

<build>
    <plugins>
        <plugin>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-maven-plugin</artifactId>
            <version>0.0.8</version>
            <configuration>
                <mainClass>com.example.App</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>

Now, you can run your app from the terminal with: mvn javafx:run

Option 2: Using Gradle

Gradle is a newer build tool that uses a Groovy or Kotlin-based DSL (Domain Specific Language) instead of XML. It is often considered more flexible and faster than Maven.

1. The build.gradle File

In your build.gradle file, you apply the JavaFX plugin and specify the version.

plugins {
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.13'
}

javafx {
    version = "21"
    modules = [ 'javafx.controls', 'javafx.fxml' ]
}

application {
    mainClass = 'com.example.App'
}

repositories {
    mavenCentral()
}

Now, you can run your app with: ./gradlew run

Option 3: Manual Setup (The Hard Way)

If you are writing a simple script and don’t want to set up a full Maven/Gradle project, you can still do it manually.

1. Download the JavaFX SDK

  1. Go to the Gluon JavaFX download page.
  2. Download the SDK for your operating system (Windows, macOS, or Linux).
  3. Extract the downloaded zip file to a permanent location on your computer (e.g., C:\Program Files\Java\javafx-sdk-21 or /Users/Shared/Java/javafx-sdk-21).

2. Configuring VS Code

If you are using Visual Studio Code with the Java Extension Pack:

  1. Open the Java Projects view in the Side Bar.
  2. Find your project and look for Referenced Libraries.
  3. Click the + icon.
  4. Navigate to the lib folder inside your downloaded JavaFX SDK.
  5. Select all the .jar files (e.g., javafx.base.jar, javafx.controls.jar, etc.) and click Select Jar Libraries.

3. Running from Command Line

When running manually, you must specify the Module Path.

java --module-path /path/to/javafx-sdk-21/lib --add-modules javafx.controls,javafx.fxml MyJavaFXApp.java
  • --module-path: Points to the lib folder of the SDK.
  • --add-modules: Lists the JavaFX modules your app needs.

Summary

  • Maven/Gradle: Best for real projects. Handles downloads and classpath automatically.
  • Manual: Good for quick experiments, but requires manual configuration every time.

For this course, we recommend trying to set up a Maven project, as it is the industry standard for Java dependency management.

Introduction to JavaFX

JavaFX is the modern standard for building rich client applications in Java. Unlike Swing, which relies on the CPU for rendering, JavaFX uses hardware acceleration (GPU) for smooth graphics. It also supports modern features like CSS styling, FXML for layout definitions, and 3D graphics.

The Structure of a JavaFX App

A JavaFX application is analogous to a theater play:

  1. Stage: The main window (the theater stage).
  2. Scene: The content currently displayed (a specific scene in the play).
  3. Nodes: The actors and props (buttons, text, shapes) arranged in a hierarchy called the Scene Graph.

A Simple JavaFX Program

To create a JavaFX application, we typically extend the javafx.application.Application class and override the start method.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class HelloFX extends Application {

    @Override
    public void start(Stage stage) {
        // 1. Create a Node (Control)
        Label label = new Label("Hello, JavaFX!");

        // 2. Create a Layout (Root Node) and add the label
        StackPane root = new StackPane();
        root.getChildren().add(label);

        // 3. Create a Scene with the root node and size
        Scene scene = new Scene(root, 300, 200);

        // 4. Set the Scene on the Stage and show it
        stage.setTitle("My First JavaFX App");
        stage.setScene(scene);
        stage.show();
    }

    // Standard main method to launch the app
    public static void main(String[] args) {
        launch();
    }
}

Key Components

  • Application: The entry point. The launch() method sets up the JavaFX runtime.
  • start(Stage stage): This is where we build our UI. The stage is provided by the system.
  • Scene: Holds the content. We pass the root node (layout) and dimensions (width, height).
  • StackPane: A simple layout pane that centers its children.

Common Controls

JavaFX comes with a huge library of UI controls. Here are a few you will use often:

  • Button: A clickable button.
  • Label: Read-only text.
  • TextField: Single-line text input.
  • TextArea: Multi-line text input.
  • CheckBox: A box that can be checked or unchecked.
  • RadioButton: Used in groups where only one can be selected.
  • ComboBox: A dropdown list of options.
  • ListView: A scrollable list of items.
Button btn = new Button("Click Me");
TextField nameField = new TextField();
CheckBox agreeBox = new CheckBox("I agree to the terms");

Layout Panes

Just like Swing has Layout Managers, JavaFX has Layout Panes. These are containers that automatically arrange their children.

1. VBox and HBox

VBox arranges children vertically in a single column. HBox arranges them horizontally in a single row.

VBox vbox = new VBox(10); // 10 pixels of spacing between items
vbox.getChildren().addAll(new Label("Name:"), new TextField(), new Button("Submit"));

2. GridPane

Arranges children in a flexible grid of rows and columns.

GridPane grid = new GridPane();
grid.add(new Label("Username:"), 0, 0); // Column 0, Row 0
grid.add(new TextField(), 1, 0);        // Column 1, Row 0
grid.add(new Label("Password:"), 0, 1); // Column 0, Row 1
grid.add(new PasswordField(), 1, 1);    // Column 1, Row 1

3. BorderPane

Similar to Swing’s BorderLayout, it has Top, Bottom, Left, Right, and Center regions.

Styling with CSS

One of the coolest features of JavaFX is that you can style your application using CSS (Cascading Style Sheets), just like a website.

You can apply styles directly in code:

btn.setStyle("-fx-background-color: #ff0000; -fx-text-fill: white;");

Or, better yet, load a .css file:

/* style.css */
.button {
    -fx-background-color: slateblue;
    -fx-text-fill: white;
    -fx-font-size: 14px;
}
scene.getStylesheets().add("style.css");

Example: A Login Form

Let’s put it all together to build a simple login form.

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class LoginForm extends Application {

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("JavaFX Welcome");

        // Create a GridPane layout
        GridPane grid = new GridPane();
        grid.setAlignment(Pos.CENTER);
        grid.setHgap(10);
        grid.setVgap(10);
        grid.setPadding(new Insets(25, 25, 25, 25));

        // Add Title
        Text scenetitle = new Text("Welcome");
        scenetitle.setFont(Font.font("Tahoma", FontWeight.NORMAL, 20));
        grid.add(scenetitle, 0, 0, 2, 1);

        // Add Label and TextField
        Label userName = new Label("User Name:");
        grid.add(userName, 0, 1);

        TextField userTextField = new TextField();
        grid.add(userTextField, 1, 1);

        // Add Label and PasswordField
        Label pw = new Label("Password:");
        grid.add(pw, 0, 2);

        PasswordField pwBox = new PasswordField();
        grid.add(pwBox, 1, 2);

        // Add Button
        Button btn = new Button("Sign in");
        HBox hbBtn = new HBox(10);
        hbBtn.setAlignment(Pos.BOTTOM_RIGHT);
        hbBtn.getChildren().add(btn);
        grid.add(hbBtn, 1, 4);

        // Add Text for messages
        final Text actiontarget = new Text();
        grid.add(actiontarget, 1, 6);

        // Handle Button Click
        btn.setOnAction(e -> {
            actiontarget.setFill(javafx.scene.paint.Color.FIREBRICK);
            actiontarget.setText("Sign in button pressed");
        });

        Scene scene = new Scene(grid, 300, 275);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

This example shows how to combine layouts (GridPane, HBox), controls (Label, TextField, Button), and event handling (setOnAction) to create a functional UI.

JavaFX with FXML

In our previous JavaFX examples, we built the UI using Java code (e.g., new Button(), grid.add(...)). While this works for small apps, it mixes the Presentation (how it looks) with the Logic (how it works).

In modern development, we prefer to separate these concerns. JavaFX uses an XML-based language called FXML to define the user interface.

What is FXML?

FXML is a declarative markup language. It looks a lot like HTML, but for JavaFX components.

Java Code:

VBox root = new VBox();
Button btn = new Button("Click Me");
root.getChildren().add(btn);

FXML:

<VBox xmlns:fx="http://javafx.com/fxml">
    <Button text="Click Me"/>
</VBox>

The Controller

When you define your UI in FXML, you need a Java class to handle the events (like button clicks). This is called the Controller.

We link the FXML file to the Controller using the fx:controller attribute.

1. The FXML File (hello-view.fxml)

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>

<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml"
      fx:controller="com.example.HelloController">
    <padding>
        <Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
    </padding>

    <Label fx:id="welcomeText"/>
    <Button text="Hello!" onAction="#onHelloButtonClick"/>
</VBox>
  • fx:controller: Points to the Java class that manages this view.
  • fx:id: Gives the element a name so we can access it in Java (like a variable name).
  • onAction: Specifies the method name to call when clicked.

2. The Controller Class (HelloController.java)

package com.example;

import javafx.fxml.FXML;
import javafx.scene.control.Label;

public class HelloController {
    @FXML
    private Label welcomeText;

    @FXML
    protected void onHelloButtonClick() {
        welcomeText.setText("Welcome to JavaFX Application!");
    }
}
  • @FXML: This annotation tells JavaFX to “inject” the component defined in the FXML file into this variable. The variable name must match the fx:id.

3. The Main Application (HelloApplication.java)

Finally, we need to load the FXML file in our main application.

package com.example;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;

public class HelloApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
        Scene scene = new Scene(fxmlLoader.load(), 320, 240);
        stage.setTitle("Hello!");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

Scene Builder

Writing XML by hand can be tedious. Fortunately, Gluon provides a free drag-and-drop tool called Scene Builder.

  1. Download and install Scene Builder.
  2. Open your .fxml file in Scene Builder.
  3. Drag components from the library onto the canvas.
  4. Set properties (text, color, size) in the Inspector panel.
  5. Assign fx:id and onAction methods in the Code panel.
  6. Save the file.

Scene Builder generates the FXML code for you, allowing you to design complex interfaces visually.

Why Use FXML?

  1. Separation of Concerns: Designers can work on the FXML/CSS while developers work on the Java logic.
  2. Maintainability: It’s easier to see the structure of the UI in a hierarchical XML file than in nested Java method calls.
  3. Tooling: You can use visual tools like Scene Builder.

Most professional JavaFX applications use FXML for their layouts.

Java and the Web: The Future of GUIs

Throughout this chapter, we have explored how to build desktop applications using AWT, Swing, and JavaFX. These technologies are powerful for creating standalone tools, scientific applications, and internal enterprise software.

However, if you look at the software you use every day (Gmail, Netflix, Instagram), most of it runs in a web browser or a mobile app. In the modern software landscape, Java is rarely used for the “Frontend” (the part you see). Instead, it is a dominant force in the “Backend” (the server that processes data).

The Modern Architecture: Full Stack Development

In a modern web application, the responsibilities are split into two distinct layers. This is often called “Full Stack” development when you work on both.

  1. The Frontend (Client): Runs in the user’s browser. It handles the UI, animations, and user interaction. It is almost always written in JavaScript or TypeScript, using frameworks like React, Vue, or Angular.
  2. The Backend (Server): Runs in a data center. It handles security, database access, and business logic. This is where Java shines.

How They Talk: REST APIs and JSON

Since the Frontend (JavaScript) and Backend (Java) speak different languages, they communicate using a standard format called JSON (JavaScript Object Notation) over HTTP requests. This architectural style is called REST (Representational State Transfer).

1. The Frontend asks for data: The JavaScript code running in Chrome or Safari sends a request to the server.

// JavaScript (React/Vue)
fetch('https://api.myapp.com/users/1')
  .then(response => response.json())
  .then(data => console.log(data));

2. The Backend replies with data: The Java code running on the server receives the request, looks up the user in a database, and sends back a JSON string.

{
  "id": 1,
  "name": "Alice",
  "email": "alice@example.com"
}

Going Deeper: Spring Boot

While you can write a web server in Java from scratch using raw Sockets (as we might see in a networking chapter), nobody does this in production. Instead, the industry standard is Spring Boot.

Spring Boot is a framework that makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.

Why Spring Boot?

When students learn “Advanced Java”, they often expect to learn complex Object-Oriented patterns. However, in the real world, “Advanced Java” usually means Spring.

  • Dependency Injection (DI): Spring manages your objects for you. You don’t say new DatabaseConnection(); you ask Spring to give you one.
  • Web Server Included: Spring Boot includes a web server (Tomcat) inside your app. You just run your main method, and the server starts.
  • Auto-Configuration: It guesses what you need based on the libraries you added. Added a database driver? It automatically sets up the connection pool.

A Real World Example

Let’s look at how a Spring Boot application is structured. It typically follows a Controller-Service-Repository pattern.

1. The Data Model (Record)

First, we define what our data looks like. In modern Java, we use record for this.

public record User(Long id, String name, String email) {}

2. The Controller (The Interface)

The Controller is the “Receptionist”. It accepts the HTTP request from the JavaScript frontend and decides what to do.

import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "http://localhost:3000") // Allow React to talk to us
public class UserController {

    private final UserService userService;

    // Constructor Injection: Spring provides the UserService automatically
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping
    public List<User> getAllUsers() {
        return userService.findAll();
    }

    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.findById(id);
    }
}
  • @RestController: Tells Spring this class handles web requests.
  • @GetMapping: Maps a URL (like /api/users) to a Java method.
  • @CrossOrigin: Allows the frontend (running on port 3000) to talk to the backend (running on port 8080).

3. The Service (The Logic)

The Service layer contains the business logic.

@Service
public class UserService {
    // In a real app, we would inject a Repository here to talk to a database
    
    public List<User> findAll() {
        return List.of(
            new User(1L, "Alice", "alice@example.com"),
            new User(2L, "Bob", "bob@example.com")
        );
    }

    public User findById(Long id) {
        // Simulate finding a user
        return new User(id, "Found User", "user" + id + "@example.com");
    }
}

Does this require Spring?

Strictly speaking, no. You can build Java web APIs using:

  • Jakarta EE (formerly Java EE): The official standard, but often considered more “heavyweight”.
  • Micronaut / Quarkus: Newer, faster frameworks designed for the cloud.
  • Javalin: A lightweight library for simple APIs.

However, Spring Boot is the overwhelming market leader. If you get a job as a Java Developer today, there is a 90% chance you will be writing Spring Boot code.

Summary

  • Desktop (Swing/JavaFX): Great for high-performance, offline, or complex tools (like IDEs, scientific visualizers).
  • Web (React + Spring Boot): The industry standard for consumer-facing applications.
    • Frontend: JavaScript/React handles the visuals.
    • Backend: Java/Spring Boot handles the logic and data.
    • Communication: They talk via JSON over HTTP.

As you continue your programming journey, mastering Core Java is step one. Step two is often learning Spring Boot to take your Java skills to the web.