Build Guide · 10 Sheets

JavaFX
Blueprints

Every JavaFX app is built the same way a building is: a window, a floor plan, and the fixtures inside it. Ten sheets, each one explained properly — the "what," the "why," and the mistake beginners actually make.

STAGE — the window
SCENE — the content area
NODE — a Button
01

Your First JavaFX Program

FOUNDATION · Application, Stage, start()

Every JavaFX app opens a window and then decides what to put in it. That's the whole idea of lesson one — everything else in this guide is just filling that window in.

import javafx.application.Application;
import javafx.stage.Stage;

public class MyApp extends Application {
    @Override
    public void start(Stage stage) {
        stage.setTitle("My First App");
        stage.show();
    }

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

What's actually happening here

JavaFX apps don't run like a normal Java program that executes top to bottom and finishes. They run inside a lifecycle managed by the Application class, which calls three methods for you in order:

MethodWhen it runsWhat you use it for
init()Before the window appearsOptional setup — loading data, no UI allowed here yet
start(Stage)Right after init()Build and show your UI — this is where almost all your code lives
stop()When the window closesOptional cleanup — saving files, closing connections

You never call start() yourself, and you never write new MyApp() either. Calling launch(args) is what tells the JavaFX runtime "set up the toolkit, create an instance of this class, and call its lifecycle methods for me." Trying to run a JavaFX app by directly instantiating it or calling start() by hand will fail with a toolkit-not-initialized error — launch() is doing real setup work behind the scenes, not just being polite ceremony.

Reading every line

Application — the base class every JavaFX app extends; it gives you the lifecycle above for free
Stage — the actual window; JavaFX creates one and hands it to start()
setTitle() — the text shown in the window's title bar
show() — makes the window visible; without this line the app runs and does nothing visible at all
COMMON MISTAKEForgetting stage.show(). The program compiles, runs, and appears to do nothing — no error, no window. If your app "isn't working" and there's no error message, check for a missing show() first.
TRY ITRun this exactly as written. You should see an empty titled window and nothing else — that blank window is 100% correct. Then try removing show() and re-running, just to see what "silently broken" looks like.
Why does main() only call launch(args) and nothing else?

Because launch() blocks and hands control over to the JavaFX event system until the window is closed. Code placed after launch(args) in main() won't run until the whole app has already finished.

Can I have more than one Stage?

Yes — you can create additional new Stage() windows yourself for dialogs or secondary windows. The one passed into start() is just the first, "primary" one.

02

Stage, Scene & Scene Graph

STRUCTURE · the container chain

One chain to memorize, and most of JavaFX's structure falls into place:

Stage → Scene → Root Node → Other Nodes

What each layer actually does

The Stage is the operating-system window: the frame, the minimize/close buttons, the title bar. It knows almost nothing about what's inside it.

The Scene is the entire content area inside that frame — one rectangle of pixels. A Stage can only display one Scene at a time, but you can swap that Scene out for a different one whenever you want, which is exactly how multi-screen apps work: a "settings screen" is just a second Scene you build once and call stage.setScene(settingsScene) on later.

Every Scene has exactly one root node — usually a layout like VBox or BorderPane — and everything else hangs off that root as children, and their children as grandchildren, and so on. This whole tree is called the scene graph, and it's not just a mental model: JavaFX really does walk this tree every time it needs to lay things out, repaint the screen, or figure out which node a click landed on.

Label label = new Label("Hello!");
Scene scene = new Scene(label, 400, 300);
stage.setScene(scene);
stage.show();

The two numbers, 400 and 300, are the Scene's starting width and height in pixels. They're a suggestion, not a lock — the user can still resize the window afterward unless you explicitly call stage.setResizable(false).

The one root rule

A node can only appear once in a scene graph. If you try to add the same Label to two different containers, JavaFX throws an IllegalArgumentException at runtime — it doesn't silently clone it for you. If you want the same text in two places, create two separate Label objects.

WHY IT MATTERSBecause Scenes are swappable, most real apps don't create new Stages for every screen — they build several Scenes up front (or lazily) and swap the current one on the same Stage, keeping the window itself stable while the content changes.
COMMON MISTAKEnew Scene(label, 400, 300) only works because Label can act as a root on its own. Try passing something that isn't a valid root — like a bare String — and it won't compile; the Scene constructor specifically expects a Parent (which layouts and most containers are).
03

Layouts

ARRANGEMENT · let the layout do the math

Layouts arrange your controls automatically. You should almost never place a button by hand-typed pixel coordinates — let one of these do it instead.

The four you'll use constantly

VBox — stacks children vertically, top to bottom
HBox — lines children up horizontally, left to right
BorderPane — five named zones: top, bottom, left, right, center
GridPane — rows and columns, like a spreadsheet
VBox box = new VBox(10);              // vertical stack, 10px gap between children
box.setPadding(new Insets(16));    // 16px of breathing room inside the box's own edges
box.setAlignment(Pos.CENTER);     // centers children instead of hugging the top-left

That constructor argument, 10, is the spacing between children — not padding around the outside. Padding and spacing solve different problems and beginners mix them up constantly: spacing is the gap between items, padding is the gap between the items and the container's own border.

BorderPane's five slots

BorderPane pane = new BorderPane();
pane.setTop(new Label("Menu bar"));
pane.setCenter(new Label("Main content"));
pane.setBottom(new Label("Status bar"));

Each slot holds exactly one node — but that node is very often itself a VBox or HBox holding several more. Nesting layouts inside layouts is completely normal; it's how every non-trivial JavaFX screen is actually built. The center region automatically stretches to fill whatever space top/bottom/left/right don't use, which is why it's almost always where the "main" content of a screen goes.

GridPane and coordinates

GridPane grid = new GridPane();
grid.setHgap(8);
grid.setVgap(8);
grid.add(new Label("Name:"), 0, 0);   // column 0, row 0
grid.add(new TextField(),        1, 0);   // column 1, row 0

The add() arguments are column, then row — the opposite order from how you'd say "row 0, column 1" out loud, and a very common source of "why is my grid backwards" bugs.

TRY ITSwap a VBox for an HBox in any earlier example — same code, same controls, completely different layout. Then nest an HBox of buttons inside a VBox's last slot to see layouts compose.
COMMON MISTAKESetting a fixed pixel width/height on a layout and then wondering why children get clipped or squeezed. Layouts are meant to size themselves to their content — reach for setPrefWidth only when you specifically need to override that.
04

Controls

FIXTURES · the things users touch

Controls are the nodes people actually interact with. Knowing their names is step one — knowing their two or three most useful properties is what actually lets you build something.

ControlPurposeKey methods
LabelDisplays text, non-editablesetText(), getText()
ButtonCan be clickedsetOnAction(), setDisable(true)
TextFieldOne line of typed inputgetText(), setPromptText()
TextAreaMultiple lines of typed inputsetWrapText(true)
PasswordFieldLike TextField, but masks charactersgetText()
CheckBoxAn on/off choiceisSelected()
RadioButtonPick one from a groupgrouped via ToggleGroup
ComboBox<T>A dropdown pickergetValue(), getItems().add()
SliderDrag to pick a numeric valuegetValue()
Label name = new Label("Name:");
TextField input = new TextField();
input.setPromptText("Type here...");   // greyed-out hint, disappears once typed

Button button = new Button("Click Me");
button.setDisable(true);              // greys it out and blocks clicks

Almost every control shares a small set of properties inherited from Node: setVisible(false) hides it entirely (and removes its space), setDisable(true) greys it out but keeps its space, and setPrefWidth() / setPrefHeight() suggest a size without forcing one.

Grouping radio buttons

Radio buttons only exclude each other if you explicitly put them in the same ToggleGroup — otherwise every RadioButton on screen behaves independently, which is a very common "why can I select both?!" bug.

ToggleGroup group = new ToggleGroup();
RadioButton small = new RadioButton("Small");
RadioButton large = new RadioButton("Large");
small.setToggleGroup(group);
large.setToggleGroup(group);
COMMON MISTAKECalling getText() on a TextField and expecting a number. It always returns a String — even if the user typed "42" — which is exactly the problem sheet 08 walks through solving.
05

Events & Clicks

BEHAVIOR · telling JavaFX what to do

An event is something that happens — a click, a keystroke, a mouse move. You attach a small block of code, called a handler, that runs when it does.

Button button = new Button("Click Me");
button.setOnAction(e -> {
    System.out.println("Button clicked!");
});

e -> { ... } is a lambda expression — read it as plain English: "when this happens, do this." The e is the event object itself; you often won't need to use it at all, which is exactly why so many JavaFX examples name it something short and forgettable.

Not every event is a click

HandlerFires on
setOnAction()Buttons, MenuItems — a simple "activated" event
setOnMouseClicked()Any node, gives you click position and count (single/double click)
setOnMousePressed() / setOnMouseReleased()The press and the release, separately — needed for dragging
setOnKeyPressed()A key going down while the node has focus
setOnMouseEntered() / setOnMouseExited()The cursor entering or leaving a node's bounds

Reacting across controls

The genuinely useful pattern is one control's handler updating a different control:

Label label = new Label("Not clicked");
Button button = new Button("Click");
button.setOnAction(e -> label.setText("You clicked the button!"));

VBox root = new VBox(10, label, button);
Scene scene = new Scene(root, 400, 250);

This works because the lambda can "reach out" and use label, a variable declared outside of it — as long as that variable is never reassigned after it's declared, Java lets a lambda capture it. This rule is called effectively final, and it's why you'll see beginners get a compile error the moment they try to reassign a captured variable from inside a lambda.

COMMON MISTAKEWriting button.setOnAction(e -> doSomething()); more than once on the same button expecting both to run. Each call to setOnAction replaces the previous handler — it doesn't add a second one. If you genuinely need multiple independent handlers, use addEventHandler() instead.
06

Color & Styling

FINISH · painting the fixtures

JavaFX styling reads almost exactly like CSS, just with an -fx- prefix on every property.

button.setStyle(
    "-fx-background-color: #3a7bd5; " +
    "-fx-text-fill: white; " +
    "-fx-font-size: 16px;"
);

Properties worth knowing

-fx-background-color — fill color
-fx-text-fill — text color (not "color")
-fx-font-size / -fx-font-weight — text sizing
-fx-border-color / -fx-border-width — outline
-fx-background-radius — rounded corners
-fx-padding — inner spacing

Inline styles vs. a stylesheet file

setStyle() is fine for a one-off test, but it hardcodes appearance directly into your logic code and has to be repeated on every control. The moment you have more than a couple of styled controls, move styling into a real .css file instead:

/* style.css */
.button {
    -fx-background-color: #3a7bd5;
    -fx-text-fill: white;
}
.button:hover {
    -fx-background-color: #2c5fa8;
}
scene.getStylesheets().add("style.css");

Every JavaFX control already has a default style class matching its type in lowercase — Button.button, Label.label — so the CSS above styles every button in the scene at once. Add your own class with node.getStyleClass().add("danger") to target specific controls without touching every button in the app. Pseudo-classes like :hover and :pressed only work in an actual stylesheet — setStyle() can't express them.

LOOKING AHEADOnce a program grows past one or two screens, an external stylesheet stops being a nice-to-have and starts being the only sane way to keep visual design separate from application logic.
07

Putting It All Together

ASSEMBLY · everything so far, in one file

A text field, a button, and a label that responds — every idea from sheets 01–06 in one runnable program.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class HelloApp extends Application {
    @Override
    public void start(Stage stage) {
        Label label = new Label("Type your name:");
        TextField input = new TextField();
        Button button = new Button("Say Hello");
        Label output = new Label();

        button.setOnAction(e ->
            output.setText("Hello, " + input.getText() + "!"));

        VBox root = new VBox(10, label, input, button, output);
        Scene scene = new Scene(root, 400, 250);
        stage.setTitle("Hello App");
        stage.setScene(scene);
        stage.show();
    }

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

Tracing the flow

  1. launch(args) boots JavaFX and calls start(stage) for you.
  2. Four nodes are created in memory — nothing is on screen yet, they're just Java objects sitting in variables.
  3. setOnAction attaches a handler to button, but the lambda's code doesn't run yet — it's stored, waiting for a click.
  4. The four nodes are handed to a new VBox, which becomes the Scene's root — this is the moment they actually join the scene graph.
  5. The Scene is attached to the Stage, and show() finally paints all of it to the screen.
  6. Only now, when a user clicks the button, does the lambda from step 3 actually execute — reading whatever is currently in input at that moment, not whatever was there when the handler was written.
TRY ITLeave the field empty and click the button — you'll get "Hello, !", not an error. Then try deleting the + "!" to see the string concatenation actually change. Small deliberate breakages like this teach you more than reading ever will.
08

Mini Calculator

PROJECT · text in, numbers out

The whole trick of a calculator app: text fields hold text, even when it looks like a number, so you have to convert it first — and handle it politely when the user types something that isn't a number at all.

double a = Double.parseDouble(first.getText());
double b = Double.parseDouble(second.getText());
double answer = a + b;
result.setText("Answer = " + answer);

A fuller version, with all four operators

plusButton.setOnAction(e -> calculate("+"));
minusButton.setOnAction(e -> calculate("-"));
timesButton.setOnAction(e -> calculate("*"));
divideButton.setOnAction(e -> calculate("/"));

private void calculate(String op) {
    try {
        double a = Double.parseDouble(first.getText());
        double b = Double.parseDouble(second.getText());
        double answer = switch (op) {
            case "+" -> a + b;
            case "-" -> a - b;
            case "*" -> a * b;
            case "/" -> a / b;
            default -> 0;
        };
        result.setText("Answer = " + answer);
    } catch (NumberFormatException ex) {
        result.setText("Please enter valid numbers");
    }
}

The try / catch matters more than it looks like it should. Without it, typing letters into either field crashes the calculation with an unhandled NumberFormatException — catching it and showing a friendly message is the difference between a toy and something that survives real user input. Dividing by zero, by contrast, won't crash at all: double division by zero produces Infinity rather than throwing, which is worth testing for on purpose.

CHALLENGEAdd a "Clear" button that resets both fields and the result label. Then try switching from double to int parsing and see which of your test cases break.
09

Scratch-Like Ideas

FRONTIER · a draggable workspace

Yes — JavaFX can genuinely power something in the spirit of Scratch: a free canvas of objects you can grab, drag, and command with buttons. It needs a different kind of container than everything so far.

Why a Pane, and not a VBox

Every layout in sheet 03 actively arranges its children for you — that's the opposite of what a drag-and-drop workspace needs. A plain Pane does no automatic layout at all: children stay exactly where you put them, which is precisely why it's the right container for freely positioned, draggable objects.

Pane — a free-form workspace with no auto-layout
Rectangle / Circle / ImageView — draggable sprites
setOnMousePressed() — detect the grab, remember the offset
setOnMouseDragged() — move it live as the mouse moves
Buttons — Run, Stop, Add Block commands
ObservableList — track every block on the board reactively
Rectangle sprite = new Rectangle(60, 60);
double[] offset = new double[2];

sprite.setOnMousePressed(e -> {
    offset[0] = e.getX() - sprite.getLayoutX();
    offset[1] = e.getY() - sprite.getLayoutY();
});

sprite.setOnMouseDragged(e -> {
    sprite.setLayoutX(e.getX() - offset[0]);
    sprite.setLayoutY(e.getY() - offset[1]);
});

Pane workspace = new Pane(sprite);

The version from the original guide moved the rectangle straight to the cursor position, which makes it snap so its top-left corner sits under your mouse the instant you grab it anywhere. Capturing the offset on press — the gap between where you clicked and the shape's current corner — and subtracting it during drag keeps the shape glued to wherever you actually grabbed it. This is the single most common polish fix beginners discover when their first drag implementation feels "off."

Two coordinate systems, easy to confuse

getX() / getY() on a mouse event are relative to the node the handler is attached to. setLayoutX() / setLayoutY() position a node relative to its parent. Mixing these up — using a coordinate meant for one system in the other — is the usual cause of a sprite that drifts or jumps when dragged.

SCOPE CHECKThis is the seed of a drag-and-drop editor, not the whole thing. A full Scratch clone needs snapping blocks together, an execution engine for "running" the assembled blocks, and a save/load format — a real project to build toward step by step, not in one sitting.
10

Your Learning Path

ROADMAP · what to build next, in order

A realistic order to actually get good at this — each step leans on the one before it. Rushing ahead to step 8 without steps 1–4 solid is the most common reason beginners stall out.

1. Stage, Scene, Node — the container chain from sheet 02, until it's automatic

2. VBox, HBox, GridPane — get comfortable arranging things without fighting the layout

3. Label, Button, TextField — the everyday controls, plus their common properties

4. Button events & lambdas — make things respond, including invalid input

5. Pane & mouse events — free-form movement, offsets, and coordinate systems

6. Build a calculator — your first real logic, with error handling that doesn't crash

7. Build a small drawing app — combine shapes, mouse events, and a Canvas

8. Build a drag-and-drop block editor — the Scratch-like project, block snapping included

9. Add your own blocks and commands — make it yours, and start reading the official JavaFX docs directly

Where to go for real documentation

Once you're past these basics, the two resources worth bookmarking are the official OpenJFX documentation and the JavaFX CSS Reference Guide — both are dense, but they're the ground truth for exactly which properties and methods exist on which class, which no beginner guide (including this one) can fully replace.

Practice Bench

Tap each one off as you build it. Nothing saves when you leave — so if you're serious, keep the code, not the checkmarks.

Beginner Practice Tasks

0 / 6 built
Make a button that changes a label
Make a counter with +1 and −1 buttons
Make a simple calculator that handles bad input gracefully
Make a circle you can drag around without it jumping
Make a small drawing board
Make a Scratch-style workspace with draggable blocks