How do I create a Spirograph animation in Java Swing?

You can create a spirograph-style animation in Java Swing by calculating points on a hypotrochoid curve and connecting them with short line segments. Drawing progressively more segments makes a bright point appear to trace the pattern.

Spirograph Bloom

Spirograph Bloom

In this SwingLoops example, a rainbow flower takes shape over time, pauses, and fades away. The application uses only Java Swing and Java 2D, without external libraries or image assets.

Source code

Save the following code as SpirographBloom.java:

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;

public class SpirographBloom extends JPanel {
    private static final double CYCLE = 12.0;
    private static final int SAMPLES = 2400;
    private static final double R = 150;
    private static final double r = 40;
    private static final double d = 95;
    private static final double END = 8 * Math.PI;

    private final long start = System.nanoTime();
    private final Timer timer = new Timer(16, _ -> repaint());

    public SpirographBloom() {
        setPreferredSize(new Dimension(540, 960));
        setBackground(new Color(6, 9, 22));
    }

    @Override
    public void addNotify() {
        super.addNotify();
        timer.start();
    }

    @Override
    public void removeNotify() {
        timer.stop();
        super.removeNotify();
    }

    private static Point2D point(double t) {
        double ratio = (R - r) / r;
        double x = (R - r) * Math.cos(t)
                + d * Math.cos(ratio * t);
        double y = (R - r) * Math.sin(t)
                - d * Math.sin(ratio * t);
        return new Point2D.Double(x, y);
    }

    @Override
    protected void paintComponent(Graphics graphics) {
        super.paintComponent(graphics);
        Graphics2D g = (Graphics2D) graphics.create();
        try {
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            double scale = Math.min(
                    getWidth() / 540.0, getHeight() / 960.0);
            g.translate(getWidth() / 2.0, getHeight() / 2.0);
            g.scale(scale, scale);

            double seconds = (System.nanoTime() - start) / 1e9;
            double phase = (seconds % CYCLE) / CYCLE;
            double progress = Math.min(phase / 0.8, 1.0);
            float fade = phase < 0.9
                    ? 1f
                    : (float) Math.max(0, (1 - phase) / 0.1);
            int visible = (int) (progress * SAMPLES);

            for (int i = 1; i <= visible; i++) {
                double t0 = END * (i - 1) / SAMPLES;
                double t1 = END * i / SAMPLES;
                Point2D a = point(t0);
                Point2D b = point(t1);
                Shape segment = new Line2D.Double(a, b);
                Color color = Color.getHSBColor(
                        i / (float) SAMPLES, 0.75f, 1f);
                g.setColor(color);
                g.setComposite(
                        AlphaComposite.SrcOver.derive(fade * 0.12f));
                g.setStroke(new BasicStroke(9f,
                        BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
                g.draw(segment);
                g.setComposite(
                        AlphaComposite.SrcOver.derive(fade));
                g.setStroke(new BasicStroke(2f,
                        BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
                g.draw(segment);
            }

            Point2D tip = point(END * progress);
            g.setComposite(AlphaComposite.SrcOver.derive(fade));
            g.setColor(new Color(245, 250, 255));
            g.fill(new Ellipse2D.Double(
                    tip.getX() - 5, tip.getY() - 5, 10, 10));
        } finally {
            g.dispose();
        }
    }

    static void main() {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("SwingLoops - Spirograph Bloom");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setContentPane(new SpirographBloom());
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

Run the example

You need a Java Development Kit version 25 and a desktop environment that supports Swing windows. Open a terminal in the directory containing the source file:

javac SpirographBloom.java
java SpirographBloom

Compilation was verified with JDK 25. Visual playback has not yet been verified.

The window has a preferred drawing area of 540 × 960 pixels. If it is too tall for your screen, change new Dimension(540, 960) to new Dimension(405, 720). Close the window to stop the application.

What is a hypotrochoid?

Imagine a small circle rolling without slipping inside a larger circle. Attach a drawing point to the small circle. As the circle rolls, the point traces a curve called a hypotrochoid.

The point can lie inside, on, or beyond the edge of the rolling circle. In this example, it lies beyond the edge, creating loops that resemble flower petals.

The curve is described by two equations:

k = (R - r) / r

x(t) = (R - r) × cos(t) + d × cos(k × t)
y(t) = (R - r) × sin(t) - d × sin(k × t)

Here, R is the fixed outer circle’s radius, r is the rolling circle’s radius, and d is the distance from the rolling circle’s center to the drawing point. The parameter t is an angle measured in radians; it is not elapsed time in seconds.

The first part of each equation locates the rolling circle’s center. That center travels around a circle of radius R - r.

The second part adds the drawing point’s offset from the rolling center. The minus sign in the vertical term accounts for the opposite rotation associated with rolling inside the outer circle.

The code implements these equations in point():

double ratio = (R - r) / r;
double x = (R - r) * Math.cos(t)
        + d * Math.cos(ratio * t);
double y = (R - r) * Math.sin(t)
        - d * Math.sin(ratio * t);

Swing’s vertical axis points downward, so the result is reflected vertically relative to the usual mathematical coordinate system. Neither construction circle is drawn; only the resulting curve is visible.

Why does the curve close at 8π?

For the chosen radii:

R = 150
r = 40
k = (150 - 40) / 40 = 11/4

Both angular components must return to their starting orientations for the complete tracing state to repeat.

At t = 8π:

First angle:   t = 8π         → 4 complete revolutions
Second angle:  (11/4)t = 22π  → 11 complete revolutions

This is why the example uses:

private static final double END = 8 * Math.PI;

For positive integer radii with R > r, a valid closure interval is:

END = 2π × r / gcd(R, r)

The greatest common divisor of 150 and 40 is 10, giving 2π × 40 / 10 = 8π. If you change the radii, revisit this interval rather than keeping END unchanged.

Revealing the curve over time

The animation converts elapsed seconds into a repeating phase between zero and one:

double phase = (seconds % CYCLE) / CYCLE;

The modulo operator resets the phase after every 12 seconds. Drawing occupies the first 80% of the cycle:

double progress = Math.min(phase / 0.8, 1.0);
int visible = (int) (progress * SAMPLES);

At six seconds, phase is 0.5 and progress is 0.625. The panel draws 1,500 of its 2,400 segments. At 9.6 seconds, the entire curve is visible.

Each segment joins two neighboring samples:

double t0 = END * (i - 1) / SAMPLES;
double t1 = END * i / SAMPLES;

More samples make the polyline approximate the mathematical curve more closely, but also increase the painting work. The bright drawing point is evaluated directly at END * progress, so it moves continuously between the sampled segment endpoints.

Creating the rainbow glow

The hue is based on the segment’s position along the complete curve:

Color color = Color.getHSBColor(i / (float) SAMPLES, 0.75f, 1f);

This assigns a stable color to each part of the drawing. Saturation and brightness remain fixed while the hue travels around the color wheel.

Each segment is drawn twice: first with a wide, low-opacity stroke, then with a thin, brighter stroke. The overlapping translucent strokes approximate a glow without a blur filter.

Rounded stroke caps help adjacent line segments join visually.

Holding and fading the pattern

The completed pattern remains fully visible from 9.6 to 10.8 seconds. During the last 1.2 seconds, opacity decreases linearly:

float fade = phase < 0.9
        ? 1f
        : (float) Math.max(0, (1 - phase) / 0.1);

For example, at phase 0.95, opacity is 0.5. The same fade value is applied to the curve and drawing point.

The background is cleared on every repaint by super.paintComponent(graphics). The application redraws the visible curve from its mathematical definition rather than accumulating pixels between frames.

The sequence repeats, but the transition is not a perfectly seamless fade-in: the drawing point reappears at full brightness when the next cycle starts.

Resizing and Swing painting

The code moves the coordinate origin to the panel’s center and applies a uniform scale:

double scale = Math.min(getWidth() / 540.0, getHeight() / 960.0);
g.translate(getWidth() / 2.0, getHeight() / 2.0);
g.scale(scale, scale);

Using the smaller scale factor preserves the curve’s proportions when the window changes shape. With these parameters, the point stays within a distance of (R - r) + d = 205 logical pixels from the center, leaving room inside the default canvas.

A Swing timer requests repaints approximately every 16 milliseconds. This targets roughly 60 updates per second, but does not guarantee that frame rate. Positions depend on elapsed time rather than the number of timer events.

The interface is created on Swing’s event-dispatch thread. The timer starts when the panel becomes displayable and stops when it is removed. Painting uses a copied Graphics2D context, disposed in a finally block after drawing.

Customize the animation

Parameter Effect
CYCLE Changes the total cycle duration.
SAMPLES Changes the curve’s sampling detail and rendering cost.
R and r Change the curve’s geometry; recalculate END afterward.
d Changes the drawing point’s offset and the shape of the loops.
9f and 2f Change the glow and bright stroke widths.
0.75f in getHSBColor() Changes color saturation.
Background color Changes the contrast behind the glowing curve.

For a first experiment, keep R, r, and END unchanged and reduce d from 95 to 40. The drawing point then lies on the rolling circle’s edge, producing a special case called a hypocycloid.

If you increase the radii or offset, check that the curve still fits inside the drawing area.

Free source code

The complete source code is included above. Save it as SpirographBloom.java to compile and run it without additional dependencies.

Explore the SwingLoops repository for the series’ source code collection.

How do I create a pendulum wave animation in Java Swing?

You can create a pendulum wave animation in Java Swing by drawing several pendulums and giving each one a slightly different oscillation frequency. Their movements gradually form changing patterns before returning to alignment.

Pendulum Wave

Pendulum Wave

In this SwingLoops example, 15 glowing pendulums complete different numbers of swings within a shared 24-second cycle. The animation uses only Java Swing and Java 2D, without external libraries or image assets.

import javax.swing.*;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;

public class PendulumWave extends JPanel {
    private static final int COUNT = 15;
    private static final double CYCLE = 24.0;
    private final long start = System.nanoTime();
    private final Timer timer = new Timer(16, _ -> repaint());

    public PendulumWave() {
        setPreferredSize(new Dimension(540, 960));
        setBackground(new Color(7, 10, 24));
    }

    @Override
    public void addNotify() {
        super.addNotify();
        timer.start();
    }

    @Override
    public void removeNotify() {
        timer.stop();
        super.removeNotify();
    }

    @Override
    protected void paintComponent(Graphics graphics) {
        super.paintComponent(graphics);
        Graphics2D g = (Graphics2D) graphics.create();
        try {
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);

            double scale = Math.min(
                    getWidth() / 540.0, getHeight() / 960.0);
            g.translate(getWidth() / 2.0, getHeight() / 2.0);
            g.scale(scale, scale);
            g.translate(-270, -480);

            double time = (System.nanoTime() - start) / 1e9;
            double phase = (time % CYCLE) / CYCLE;

            for (int i = 0; i < COUNT; i++) {
                double pivotX = 270;
                double pivotY = 100 + i * 44;
                double length = 180;

                // Each pendulum completes an integer number
                // of swings during the shared 24-second cycle.
                double angle = 0.95 * Math.cos(
                        2 * Math.PI * (12 + i) * phase);
                double x = pivotX + length * Math.sin(angle);
                double y = pivotY + length * Math.cos(angle);

                Color color = Color.getHSBColor(
                        i / (float) COUNT, 0.75f, 1f);

                g.setComposite(AlphaComposite.SrcOver.derive(0.22f));
                g.setColor(color);
                g.setStroke(new BasicStroke(1.5f));
                g.draw(new Line2D.Double(pivotX, pivotY, x, y));

                g.setComposite(AlphaComposite.SrcOver);
                g.setColor(new Color(110, 120, 145));
                g.fill(new Ellipse2D.Double(
                        pivotX - 2, pivotY - 2, 4, 4));

                for (int glow = 4; glow >= 1; glow--) {
                    double radius = 8 + glow * 4;
                    g.setComposite(
                            AlphaComposite.SrcOver.derive(0.045f));
                    g.setColor(color);
                    g.fill(new Ellipse2D.Double(
                            x - radius, y - radius,
                            radius * 2, radius * 2));
                }

                g.setComposite(AlphaComposite.SrcOver);
                g.setColor(color);
                g.fill(new Ellipse2D.Double(x - 8, y - 8, 16, 16));
                g.setColor(new Color(255, 255, 255, 190));
                g.fill(new Ellipse2D.Double(x - 3, y - 4, 4, 4));
            }
        } finally {
            g.dispose();
        }
    }

    static void main() {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("SwingLoops - Pendulum Wave");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setContentPane(new PendulumWave());
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

Run the example

Install a Java Development Kit and open a terminal in the directory containing the source file:

javac PendulumWave.java
java PendulumWave

A desktop environment is required to display the Swing window. Close the window to stop the application.

The preferred drawing area is 540 × 960 pixels. If the window is too tall for your display, change the dimensions to 405, 720. The drawing scales proportionally when you resize the window.

Calculating the pendulum angle

Each pendulum swings, according to a cosine function:

double angle = 0.95 * Math.cos(2 * Math.PI * (12 + i) * phase);

The formula has three main parts:

  • 0.95 is the angular amplitude in radians, approximately 54.4 degrees.
  • 2 * Math.PI represents one complete oscillation of the cosine function.
  • 12 + i determines how many oscillations the pendulum completes during the shared cycle.

Cosine varies between −1 and 1, so each pendulum’s angle varies between −0.95 and 0.95 radians.

In the beginning, every cosine evaluates to 1. All pendulums therefore start at the same angle.

Why do the pendulums return to alignment?

Elapsed time is converted into a repeating phase:

double phase = (time % CYCLE) / CYCLE;

With CYCLE set to 24 seconds, the phase advances from zero toward one, then returns to zero.

The first pendulum completes 12 oscillations during that interval. Each subsequent pendulum completes one more:

Pendulum Oscillations in 24 seconds Period
First 12 2 seconds
Second 13 About 1.85 seconds
Eighth 19 About 1.26 seconds
Fifteenth 26 About 0.92 seconds

Because all oscillation counts are integers, every pendulum completes a whole number of swings at the end of 24 seconds.

For an integer n:

cos(2π × n) = 1

The pendulums return to their starting angles and the pattern repeats. During the cycle, their different frequencies produce the changing wave patterns.

Here, “alignment” means the pendulums have the same angle. Their pivots remain vertically separated.

Converting the angle into screen coordinates

Once the angle is known, the bob’s position is calculated relative to its pivot:

double x = pivotX + length * Math.sin(angle);
double y = pivotY + length * Math.cos(angle);

The angle is measured from the downward vertical direction.

When the angle is zero:

sin(0) = 0
cos(0) = 1

Therefore:

x = pivotX
y = pivotY + length

The bob hangs directly below its pivot.

A positive angle moves the bob to the right, while a negative angle moves it to the left. Swing’s screen coordinates increase downward, which is why the vertical offset is added to pivotY.

Is this a physical pendulum simulation?

This example uses prescribed sinusoidal motion to create a visual effect.

All pendulums have the same drawn length, while their frequencies are assigned independently. Real pendulum wave demonstrations commonly use different lengths to get different periods.

For a simple pendulum at small angles, the approximate period is:

T = 2π × √(L / g)

Here, L is the pendulum length and g is gravitational acceleration. Longer pendulums swing more slowly.

Our animation does not solve that physical model. Its amplitude is also larger than the small-angle range where a real pendulum closely follows sinusoidal angular motion.

Creating the glow

Each bob is surrounded by four overlapping circles:

for (int glow = 4; glow >= 1; glow--) {
    double radius = 8 + glow * 4;
    // Draw a translucent circle.
}

Each circle has low opacity. Their overlapping areas become brighter toward the center, approximating a soft glow without applying a blur filter.

An opaque-colored bob and a small pale highlight are drawn afterward.

The code explicitly restores the composite before drawing opaque elements so that the glow’s transparency does not affect them.

Updating the animation safely

A Swing timer requests a repaint approximately every 16 milliseconds:

private final Timer timer = new Timer(16, _ -> repaint());

This targets roughly 60 updates per second, but actual timing depends on the system and Swing’s event queue.

The animation calculates positions from elapsed time rather than counting frames. A delayed repaint, therefore, does not change the intended 24-second cycle.

The interface is created with SwingUtilities.invokeLater(). Timer callbacks and painting also execute on Swing’s event-dispatch thread.

Customize the animation

Try changing one parameter at a time:

Parameter Effect
COUNT Changes the number of pendulums. Larger counts may require tighter vertical spacing.
CYCLE Changes the shared cycle duration. Larger values slow every pendulum.
12 + i Changes the oscillation counts. Keep integer counts to preserve the shared loop.
0.95 Changes the maximum swing angle in radians.
length Changes the drawn string length without changing the assigned frequency.
i * 44 Changes the vertical spacing between pivots.
0.75f in getHSBColor() Changes color saturation.

For example, replacing 12 + i with 12 + 2 * i increases the frequency difference between neighboring pendulums while retaining integer oscillation counts.

Explore the SwingLoops repository on GitHub for the series’ source code collection.

How do I create a neon hexagon vortex animation in Java Swing?

You can create a neon vortex in Java Swing by drawing nested hexagons with Graphics2D and changing their size, rotation, and color over time. Layering wide, transparent strokes behind each outline produces a soft glow without external libraries.

Neon Vortex

Neon Vortex

In this SwingLoops example, rainbow hexagons expand from a dark center to create a twisting tunnel. The animation follows an eight-second cycle and uses a portrait canvas suitable for recording a short video.

Source code

Save the following code as NeonVortex.java:

import javax.swing.*;
import java.awt.*;
import java.awt.geom.Path2D;

public class NeonVortex extends JPanel {
    private final long start = System.nanoTime();
    private final Timer timer = new Timer(16, e -> repaint());

    public NeonVortex() {
        setPreferredSize(new Dimension(540, 960));
        setBackground(new Color(5, 7, 20));
    }

    @Override
    public void addNotify() {
        super.addNotify();
        timer.start();
    }

    @Override
    public void removeNotify() {
        timer.stop();
        super.removeNotify();
    }

    @Override
    protected void paintComponent(Graphics graphics) {
        super.paintComponent(graphics);
        Graphics2D g = (Graphics2D) graphics.create();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        g.translate(getWidth() / 2.0, getHeight() / 2.0);
        double scale = Math.min(getWidth() / 540.0, getHeight() / 960.0);
        g.scale(scale, scale);
        // An eight-second cycle: positions and colors repeat together.
        double phase = ((System.nanoTime() - start) / 1e9 % 8.0) / 8.0;
        for (int i = 0; i < 32; i++) {
            double depth = (i / 32.0 + phase) % 1.0;
            double radius = 12 + 690 * depth * depth;
            double angle = depth * 4 * Math.PI + phase * 2 * Math.PI;
            float hue = (float) ((depth + phase) % 1.0);
            Color color = Color.getHSBColor(hue, 0.8f, 1f);
            float opacity = (float) Math.sin(Math.PI * depth);
            Shape ring = polygon(radius, angle);
            for (int glow = 3; glow >= 1; glow--) {
                g.setComposite(AlphaComposite.SrcOver.derive(opacity * 0.07f));
                g.setColor(color);
                g.setStroke(new BasicStroke(glow * 7f,
                        BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
                g.draw(ring);
            }
            g.setComposite(AlphaComposite.SrcOver.derive(opacity));
            g.setColor(color);
            g.setStroke(new BasicStroke(2f));
            g.draw(ring);
        }
        g.dispose();
    }

    private static Shape polygon(double radius, double rotation) {
        Path2D path = new Path2D.Double();
        for (int vertex = 0; vertex < 6; vertex++) {
            double a = rotation + vertex * Math.PI / 3;
            double x = radius * Math.cos(a), y = radius * Math.sin(a);
            if (vertex == 0) path.moveTo(x, y);
            else path.lineTo(x, y);
        }
        path.closePath();
        return path;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("SwingLoops - Neon Vortex");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setContentPane(new NeonVortex());
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

Run the example

You need a JDK and a desktop environment to run this Swing application. The source was compiled successfully with JDK 25; visual playback has not yet been verified.

Open a terminal in the directory containing NeonVortex.java, then compile and run it:

javac NeonVortex.java
java NeonVortex

The application opens a window with a preferred drawing area of 540 × 960 pixels. The animation continues until you close the window. If it does not fit your screen, reduce both preferred dimensions proportionally, for example to 405 × 720.

How does the animation work?

Refreshing the panel with a Swing Timer

A javax.swing.Timer requests a repaint approximately every 16 milliseconds:

private final Timer timer = new Timer(16, e -> repaint());

This targets roughly 60 updates per second, although actual timing depends on the system and event queue. The timer starts when the panel becomes displayable and stops when it is removed.

The animation uses elapsed time rather than a frame counter, so its cycle does not depend on receiving an exact number of timer events. SwingUtilities.invokeLater() creates the interface on Swing’s event-dispatch thread, where timer callbacks and painting also run.

Moving hexagons through the tunnel

The elapsed time is converted into a value from zero up to, but not including, one:

double phase = ((System.nanoTime() - start) / 1e9 % 8.0) / 8.0;

Each of the 32 hexagons receives a different position within the cycle:

double depth = (i / 32.0 + phase) % 1.0;
double radius = 12 + 690 * depth * depth;

As depth increases, the hexagon grows. Squaring depth makes growth slower near the center and faster toward the outside, suggesting motion toward the viewer. The modulo operation returns each hexagon to the center after it reaches the end of its cycle.

Drawing and rotating a hexagon

The polygon() method places six vertices around a circle. Each vertex is separated by 60 degrees, or Math.PI / 3 radians:

double a = rotation + vertex * Math.PI / 3;
double x = radius * Math.cos(a);
double y = radius * Math.sin(a);

A Path2D connects the vertices and closes the shape. The rotation combines the hexagon’s depth with the current animation phase:

double angle = depth * 4 * Math.PI + phase * 2 * Math.PI;

Different depths produce different orientations, while the phase rotates the tunnel over time.

Creating rainbow colors and a neon glow

Color.getHSBColor() converts a changing hue into a vivid RGB color. Saturation and brightness stay fixed while the hue moves around the color wheel.

For the glow, the application draws each hexagon three times using wide strokes with low opacity. A thin, brighter outline is drawn on top. This approximates a neon glow using ordinary strokes rather than a blur filter.

The opacity follows a sine curve:

float opacity = (float) Math.sin(Math.PI * depth);

Each hexagon fades in near the center, becomes brighter midway through its journey, and fades out near the outside. This also softens the moment when its depth wraps back to zero.

Preserving the aspect ratio

Before drawing, the code moves the coordinate origin to the panel’s center and applies a uniform scale based on the available width and height. Resizing the window therefore preserves the hexagons’ proportions instead of stretching them.

The drawing uses a copied Graphics2D context, which is disposed after painting so that its transforms and drawing settings do not affect other painting operations.

Customize the animation

Try changing one parameter at a time:

Parameter Effect
8.0 in the phase calculation Cycle duration in seconds; a larger value slows the animation. Change both occurrences together.
32 in the loop and depth calculation Number of hexagons; change both occurrences together.
690 in the radius calculation Maximum expansion distance.
depth * 4 * Math.PI Amount of twist across the tunnel.
glow * 7f Width of the glow strokes.
new Color(5, 7, 20) Background color.

The complete source code above is free to copy and run. No external libraries or image assets are required.

How do I extend a class in Java?

You can use the extends keyword to create a class that inherits fields and methods from another class.

The existing class is called the superclass. The new class is called the subclass.

In this example, you will create an Ebook class that extends a Book class. An ebook has a title and an author, just like a book, plus a file format.

Source code

public class InheritanceExample {
    void main() {
        Ebook ebook = new Ebook(
                "Learning Java", "Alice", "PDF");

        ebook.printDetails();
        IO.println("File format: " + ebook.getFileFormat());
    }
}

class Book {
    private final String title;
    private final String author;

    Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    public void printDetails() {
        IO.println("Title: " + title);
        IO.println("Author: " + author);
    }
}

class Ebook extends Book {
    private final String fileFormat;

    Ebook(String title, String author, String fileFormat) {
        super(title, author);
        this.fileFormat = fileFormat;
    }

    public String getFileFormat() {
        return fileFormat;
    }
}

Explanation

The Book class stores a title and an author. It also provides a printDetails() method to display those values.

The following declaration makes Ebook a subclass of Book:

class Ebook extends Book {

An Ebook inherits the accessible methods of Book, including its public printDetails() method. It also declares its own field, fileFormat.

Calling the superclass constructor

The Ebook constructor accepts values for both the book details and the file format:

Ebook(String title, String author, String fileFormat) {
    super(title, author);
    this.fileFormat = fileFormat;
}

The statement super(title, author) calls the Book constructor to initialize the title and author.

Constructors are not inherited. The subclass declares its own constructor and calls an appropriate superclass constructor.

Using an inherited method

After creating an Ebook, you can call:

ebook.printDetails();

Although Ebook does not declare printDetails(), it inherits that method from Book.

The method runs on the ebook object and prints the title and author initialized by the superclass constructor.

Accessing subclass data

The subclass provides a getter for its additional field:

public String getFileFormat() {
    return fileFormat;
}

The calling code uses this method to read the ebook’s file format:

IO.println("File format: " + ebook.getFileFormat());

The superclass fields remain private. Code inside Ebook cannot access title or author directly; the Book class manages those fields.

Run the example

Compile and run the file:

javac InheritanceExample.java
java InheritanceExample

Output

Title: Learning Java
Author: Alice
File format: PDF

Use inheritance when the subclass represents a more specific kind of the superclass. In this example, an ebook is a book with an additional file format.

How do I access private fields using getters and setters in Java?

You can use getter and setter methods to read and update private fields. A getter returns a field’s value, while a setter changes it.

These methods let a class control how its data is accessed and updated.

In this example, you will create a Book class with a private title field. A setter will prevent the title from being null or blank.

Source code

public class GetterSetterExample {
    void main() {
        Book book = new Book("Learning Java");

        IO.println("Original title: " + book.getTitle());

        book.setTitle("Exploring Java");

        IO.println("Updated title: " + book.getTitle());
    }
}

class Book {
    private String title;

    Book(String title) {
        this.title = validateTitle(title);
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = validateTitle(title);
    }

    private static String validateTitle(String title) {
        if (title == null || title.isBlank()) {
            throw new IllegalArgumentException(
                    "Title must not be null or blank.");
        }

        return title;
    }
}

Explanation

The title field is private:

private String title;

Code in GetterSetterExample cannot access this field directly. For example, the following statement would cause a compilation error:

book.title = "Exploring Java";

Instead, the class provides methods for reading and updating the value.

Reading a value with a getter

The getTitle() method returns the current title:

public String getTitle() {
    return title;
}

The calling code uses it like this:

IO.println("Original title: " + book.getTitle());

A getter commonly uses the prefix get followed by the field name, such as getTitle().

Updating a value with a setter

The setTitle() method accepts a new title:

public void setTitle(String title) {
    this.title = validateTitle(title);
}

It validates the input before assigning it to the field. If validation fails, the assignment does not happen, and the existing title stays unchanged.

The calling code updates the title using:

book.setTitle("Exploring Java");

Validating the title

Both the constructor and setter call the same validation method:

private static String validateTitle(String title) {
    if (title == null || title.isBlank()) {
        throw new IllegalArgumentException(
                "Title must not be null or blank.");
    }

    return title;
}

The method rejects null, empty strings, and strings containing only whitespace.

The || operator evaluates the second condition only when the first condition is false. If title is null, Java skips title.isBlank(), avoiding a NullPointerException.

The validation method is private because it is an implementation detail. It is static because it does not use any object’s fields.

Run the example

Compile and run the file:

javac GetterSetterExample.java
java GetterSetterExample

Output

Original title: Learning Java
Updated title: Exploring Java

Private fields with controlled access are an example of encapsulation. Add setters only for values that callers should be allowed to change. A field that should remain fixed can be initialized through a constructor and exposed through a getter.