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.

SwingLoops Neon Vortex

SwingLoops 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.

How do I set initial object values using a constructor in Java?

A constructor initializes an object when you create it using the new keyword. You can pass values to the constructor to set the object’s initial state.

In this example, you will create a Book class with a constructor that accepts a title and an author.

Source code

public class ConstructorExample {
    void main() {
        Book firstBook = new Book("Learning Java", "Alice");
        Book secondBook = new Book("Exploring Java", "Bob");

        firstBook.printDetails();
        IO.println();
        secondBook.printDetails();
    }
}

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

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

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

Explanation

The Book class declares two fields:

private final String title;
private final String author;

The private modifier limits direct access to these fields to the Book class. The final modifier means each field can be assigned only once.

The constructor receives the initial field values:

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

A constructor has the same name as its class and does not declare a return type, not even void.

Here, the parameter names match the field names. The keyword this refers to the current object, so this.title identifies the field while title identifies the parameter.

For example:

this.title = title;

This statement assigns the value of the title parameter to the current object’s title field.

The following expression creates a book and passes two arguments to its constructor:

new Book("Learning Java", "Alice")

The constructor sets the new object’s title to "Learning Java" and its author to "Alice".

Each call to new Book(...) creates a separate object with its own field values.

Run the example

Compile and run the file:

javac ConstructorExample.java
java ConstructorExample

Output

Title: Learning Java
Author: Alice

Title: Exploring Java
Author: Bob

What happens to the default constructor?

Java provides a default constructor only when a class declares no constructors.

Since this Book class declares a constructor with two parameters, the following expression causes a compilation error:

new Book()

To create a Book, supply the title and author, or explicitly add a constructor with no parameters.

How do I create an object from a class in Java?

A class defines the data and behavior of its objects. You can create an object using the new keyword, then access its fields and call its methods.

In this example, you will create two objects from a Book class. Each object stores its own title and author.

Source code

public class ObjectCreationExample {
    void main() {
        Book firstBook = new Book();
        firstBook.title = "Learning Java";
        firstBook.author = "Alice";

        Book secondBook = new Book();
        secondBook.title = "Exploring Java";
        secondBook.author = "Bob";

        firstBook.printDetails();
        secondBook.printDetails();
    }
}

class Book {
    String title;
    String author;

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

Explanation

The Book class declares two fields and one method:

class Book {
    String title;
    String author;

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

The fields title and author hold the data for each book. The printDetails() method prints that data.

The following statement creates a Book object:

Book firstBook = new Book();

Each part has a purpose:

Part Purpose
Book Declares the variable’s type
firstBook Names the variable that refers to the object
new Book() Creates and initializes a new Book object

Because Book does not declare a constructor, Java provides a default constructor with no parameters.

After creating the object, you can assign values to its fields using the dot operator:

firstBook.title = "Learning Java";
firstBook.author = "Alice";

You also use the dot operator to call a method:

firstBook.printDetails();

The second call to new Book() creates a separate object:

Book secondBook = new Book();

Each object has its own instance fields. Setting the title of secondBook does not change the title of firstBook.

Both classes are placed in the same source file. Only ObjectCreationExample is declared public, so the file is named ObjectCreationExample.java.

Run the example

Compile and run the file:

javac ObjectCreationExample.java
java ObjectCreationExample

Output

Title: Learning Java
Author: Alice

Title: Exploring Java
Author: Bob

This example assigns fields directly to show how objects work. In the next example, you will use a constructor to supply these values when creating each object.