How do I build a string using StringBuilder in Java?

You can use StringBuilder to build text by appending values to a mutable sequence of characters.

In this example, you will combine a book’s title, author, and publication year into one string.

Source code

Save the following code as StringBuilderExample.java:

public class StringBuilderExample {
    void main() {
        String title = "Learning Java";
        String author = "Alice";
        int year = 2026;

        StringBuilder builder = new StringBuilder();

        builder.append("Title: ")
                .append(title)
                .append(System.lineSeparator())
                .append("Author: ")
                .append(author)
                .append(System.lineSeparator())
                .append("Year: ")
                .append(year);

        String details = builder.toString();

        IO.println(details);
    }
}

Explanation

Strings are immutable, so their contents cannot be changed after creation. A StringBuilder provides a mutable buffer that you can update while building text.

Create an empty builder using its constructor:

StringBuilder builder = new StringBuilder();

StringBuilder belongs to java.lang, so no import is required.

Appending values

The append() method adds a value to the end of the builder:

builder.append("Title: ");
builder.append(title);

These calls produce:

Title: Learning Java

You can append strings, characters, numbers, and other values. For example, the following call appends the text representation of the integer year:

builder.append(year);

Chaining method calls

Each append() call returns the same builder, allowing you to chain calls:

builder.append("Title: ")
        .append(title);

This updates one builder. It does not create a separate builder for each call.

The example uses System.lineSeparator() to insert the platform’s line separator between the book details.

Creating the final string

After building the text, call toString():

String details = builder.toString();

This returns a String containing the builder’s current text. Later changes to the builder do not change that string.

Finally, IO.println(details) prints the result.

Run the example

Compile and run the file:

javac StringBuilderExample.java
java StringBuilderExample

Output

Title: Learning Java
Author: Alice
Year: 2026

Use StringBuilder when you need to build text through repeated updates, especially inside a loop. For a short, fixed expression, string concatenation with + is often easier to read.

How do I build my first Android App with Kotlin and Jetpack Compose?

In this first article of the Android with Kotlin and Jetpack Compose series, you will create Task Notes, a small app for keeping track of daily tasks. Today you will run the starter project and build the screen users see when their task list is empty. You will reuse this project throughout the series, all the way to preparing a release for Google Play.

Kotlin is the language you will use to write the app. Jetpack Compose is the toolkit you will use to describe what appears on the screen. For example, a Compose Text() call adds text to your interface.

1. Install Android Studio

Download the stable version of Android Studio and complete its setup wizard. Let it download the Android SDK and the recommended components. The first setup and build need an internet connection.

You can run the app on an Android emulator or on an Android phone. The emulator is a virtual Android device that runs on your computer.

2. Create the Task Notes project

  1. Select New Project from the welcome screen. If a project is already open, choose File > New > New Project.
  2. Under the phone and tablet templates, choose Empty Activity and click Next.
  3. Enter the following values and click Finish.
Setting Value
Name Task Notes
Package name org.kodejava.tasknotes
Minimum SDK API 24 (Android 7.0) for this tutorial
Build configuration language Kotlin DSL, if the wizard offers a choice

Use a package name under your own namespace if you plan to publish your own version. Android Studio normally uses the package name you enter here as the initial application ID. The minimum SDK determines the oldest Android version on which the app can be installed.

Wait for Gradle sync to finish. Gradle reads the project’s build configuration and downloads the libraries it needs. When the editor opens, find MainActivity.kt in the app module. With the sample package name, its path is app/src/main/java/org/kodejava/tasknotes/MainActivity.kt.

Reference: Create a project with Compose.

3. Run the generated app

To use an emulator, open Tools > Device Manager, create a virtual phone, and select a system image with API 24 or higher. Download the image if prompted. Select the virtual device in the toolbar and click Run.

To use a phone, enable Developer options and USB debugging, connect it with a USB data cable, and accept the debugging prompt on the phone. Select the phone in Android Studio and click Run.

Android Studio builds and installs the app on the selected device. Once the template screen opens, you can move on to editing it.

References: Run apps on the Android Emulator and Run apps on a hardware device.

4. Build the empty task screen

Replace the contents of MainActivity.kt with the following code. If you chose a different package name, keep your original package line and use the theme import and function name from your generated activity.

package org.kodejava.tasknotes

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.kodejava.tasknotes.ui.theme.TaskNotesTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()

        setContent {
            TaskNotesTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    TaskNotesScreen(modifier = Modifier.padding(innerPadding))
                }
            }
        }
    }
}

@Composable
private fun TaskNotesScreen(modifier: Modifier = Modifier) {
    Column(
        modifier = modifier.fillMaxSize().padding(24.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        Text(
            text = "Task Notes",
            style = MaterialTheme.typography.headlineMedium
        )
        Text(
            text = "No tasks yet.",
            style = MaterialTheme.typography.bodyLarge
        )
    }
}

@Preview(showBackground = true)
@Composable
private fun TaskNotesScreenPreview() {
    TaskNotesTheme {
        TaskNotesScreen()
    }
}

When Android creates MainActivity, it calls onCreate(). Inside that method, setContent { ... } supplies the activity’s Compose UI, and TaskNotesTheme provides the app’s colors and text styles.

@Composable marks a function that can describe UI using other composable functions. Here, TaskNotesScreen() uses a Column to stack two Text() elements vertically. The 8.dp value adds space between them. Android uses density-independent pixels, or dp, to keep layout sizes consistent across screens with different pixel densities.

The app draws edge to edge. Passing innerPadding from Scaffold to the screen keeps the text clear of the system bars. The screen adds another 24.dp of padding for its own layout.

5. Check the result

Click Run again. You should see Task Notes near the top of the screen, with No tasks yet. on the line below. To inspect the layout in Android Studio, open the Split or Design view and build or refresh the preview if prompted. The @Preview function renders the screen in the editor.

If the theme import is unresolved, open the generated ui/theme/Theme.kt and check its package and theme function name. If Compose imports are unresolved, check that you selected Empty Activity and that Gradle sync completed successfully.

Practice

Change the message to Your task list is empty. and increase the space between the title and the message from 8.dp to 16.dp. Check the preview, then run the app to see the same changes on your device.

Gradle dependencies

The Empty Activity template sets up the Compose build configuration and dependencies for this example. Keep the generated entries in app/build.gradle.kts and, if the project uses one, gradle/libs.versions.toml. The code uses Activity Compose, Compose Material 3, and Compose UI Tooling Preview. This article requires no additional dependencies.

Next in the series

In the next article, How Do I Use Kotlin to Model Tasks in an Android App?, you will create a Kotlin Task data class with an ID, a title, and a completion flag. You will use that model to create the sample tasks for the list screen.

How do I animate wave interference in Java Swing?

Drop two pebbles into water and the ripples overlap. Where two crests meet, they reinforce each other; where a crest meets an equally strong trough, they cancel. In this SwingLoops example, we use two sinusoidal waves to recreate that pattern pixel by pixel.

The result is a portrait animation with cyan crests, pink troughs, and dark bands where the combined displacement is small. The example uses Java Swing and Java 2D without external libraries.

Ripple Interference

Ripple Interference

Source code

Save the following code as RippleInterference.java:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class RippleInterference extends JPanel {
    private static final int W = 180, H = 320;
    private static final int AX = 52, BX = 128, SY = 160;
    private static final double TAU = 2 * Math.PI;
    private static final double CYCLE = 8.0;
    private static final Color BACKGROUND = new Color(6, 12, 26);

    private final BufferedImage image =
            new BufferedImage(W, H, BufferedImage.TYPE_INT_RGB);
    private final int[] pixels =
            ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
    private final float[] sine = new float[W * H];
    private final float[] cosine = new float[W * H];
    private final Timer timer = new Timer(33, _ -> repaint());
    private long start;

    public RippleInterference() {
        setPreferredSize(new Dimension(450, 800));
        setBackground(BACKGROUND);
        for (int y = 0; y < H; y++) {
            for (int x = 0; x < W; x++) {
                int i = y * W + x;
                double a = Math.hypot(x - AX, y - SY);
                double b = Math.hypot(x - BX, y - SY);
                double ka = TAU * a / 21, kb = TAU * b / 21;
                double wa = 1 / (1 + a / 110);
                double wb = 1 / (1 + b / 110);
                sine[i] = (float) (wa * Math.sin(ka) + wb * Math.sin(kb));
                cosine[i] = (float) (wa * Math.cos(ka) + wb * Math.cos(kb));
            }
        }
    }

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

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

    private void render(double seconds) {
        double phase = TAU * 5 * ((seconds % CYCLE) / CYCLE);
        double c = Math.cos(phase), s = Math.sin(phase);
        for (int i = 0; i < pixels.length; i++) {
            // sin(k*d - phase), summed across both sources.
            double wave = sine[i] * c - cosine[i] * s;
            double glow = Math.min(1, Math.abs(wave) / 1.5);
            glow *= glow;
            int r = 6 + (int) (glow * (wave >= 0 ? 32 : 238));
            int g = 12 + (int) (glow * (wave >= 0 ? 222 : 98));
            int b = 26 + (int) (glow * (wave >= 0 ? 229 : 143));
            pixels[i] = (r << 16) | (g << 8) | b;
        }
    }

    @Override
    protected void paintComponent(Graphics graphics) {
        super.paintComponent(graphics);
        drawFrame(graphics, getWidth(), getHeight(),
                  (System.nanoTime() - start) / 1e9);
    }

    void drawFrame(Graphics graphics, int width, int height, double seconds) {
        render(seconds);

        Graphics2D g = (Graphics2D) graphics.create();
        try {
            double scale = Math.min(width / (double) W,
                                    height / (double) H);
            g.translate((width - W * scale) / 2,
                        (height - H * scale) / 2);
            g.scale(scale, scale);
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                               RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g.drawImage(image, 0, 0, null);
            g.setColor(new Color(237, 249, 255));
            g.fillOval(AX - 2, SY - 2, 4, 4);
            g.fillOval(BX - 2, SY - 2, 4, 4);
        } finally {
            g.dispose();
        }
    }

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

Run the example

From the directory containing the source file, run:

javac RippleInterference.java
java RippleInterference

The default window is 450 × 800 pixels. Resize it to see the pattern scale while keeping its aspect ratio. Close the window to exit.

Calculate a ripple at each pixel

A circular wave depends on how far the pixel is from its source. The program finds that distance with Math.hypot():

double a = Math.hypot(x - AX, y - SY);
double ka = TAU * a / 21;
double wa = 1 / (1 + a / 110);

Here, TAU is one full turn in radians, or 2π. Dividing by 21 means the spatial phase advances by one turn every 21 image pixels. The factor wa makes the amplitude smaller as distance increases.

A source contributes wa × sin(ka − phase). As the shared time phase increases, points of equal phase move outward from the source. The program adds this contribution to the corresponding contribution from the second source.

The waves have the same frequency and start in phase. If the difference between the two distances is a whole wavelength, their oscillations reinforce each other. At half a wavelength, one oscillation opposes the other. Their amplitudes may differ because of the distance weights, so those opposing waves can leave a small residual instead of canceling completely.

Cache the parts that depend on position

Calculating each distance and calling sine twice for every pixel on every repaint would repeat work. The sources stay fixed, so the code uses the identity:

sin(k − p) = sin(k) cos(p) − cos(k) sin(p)

For each pixel, the constructor caches the sum of the two weighted spatial sines and the sum of the two weighted spatial cosines. During painting, it only needs the sine and cosine of the time phase:

double c = Math.cos(phase), s = Math.sin(phase);
double wave = sine[i] * c - cosine[i] * s;

The same values of c and s apply to all 57,600 pixels. With five turns of phase per eight-second interval, each oscillation takes 1.6 seconds.

Convert displacement to color

The sign selects cyan or pink. The magnitude controls how bright that color becomes:

double glow = Math.min(1, Math.abs(wave) / 1.5);
glow *= glow;

Squaring the normalized magnitude makes weaker values darker and emphasizes stronger peaks and troughs. A dark pixel shows low displacement at that instant; the dark lanes that persist as the ripples move reveal where the waves repeatedly oppose each other.

The renderer writes each color into an integer-backed BufferedImage and then scales the finished image with bilinear interpolation. Working at 180 × 320 keeps the per-frame calculations small while the window can display the image at a larger size.

Animate the result with Swing

A Swing timer requests a repaint approximately every 33 milliseconds. The animation reads time with System.nanoTime() instead of counting repaint events, so a late frame does not slow the intended wave motion. The window and timer run on Swing’s event-dispatch thread.

The renderer is also available through drawFrame(), which accepts a time in seconds. That makes it possible to render the same pattern into an image at a chosen instant without opening the Swing window.

Try changing the pattern

Move the sources closer together and compare the spacing of the dark bands. Then change the wavelength from 21 to 28 in both phase calculations. Keeping everything else fixed lets you see how the spacing changes with wavelength.

The complete Java source is free to copy and run. Explore more examples in the SwingLoops repository.

How do I split a string into words in Java?

You can use the split() method to divide a string into smaller parts. The method accepts a regular expression that defines where to split and returns an array of strings.

In this example, you will split a sentence into words using whitespace as the separator.

Source code

Save the following code as SplitStringExample.java:

public class SplitStringExample {
    void main() {
        String text = "  Learn Java   one snippet\tat a time.  ";

        if (text.isBlank()) {
            IO.println("The text contains no words.");
            return;
        }

        String[] words = text.strip().split("\\p{javaWhitespace}+");

        for (String word : words) {
            IO.println(word);
        }

        IO.println("Total words: " + words.length);
    }
}

Explanation

The input contains spaces at both ends, multiple spaces between some words, and a tab character:

String text = "  Learn Java   one snippet\tat a time.  ";

The escape sequence \t represents a tab.

Checking for blank text

Before splitting, the program checks whether the text is empty or contains only whitespace:

if (text.isBlank()) {
    IO.println("The text contains no words.");
    return;
}

This avoids treating an empty string as a word. The return statement ends the method when there is no text to process.

Removing surrounding whitespace

The strip() method removes leading and trailing whitespace:

text.strip()

It returns a string without changing the original string, because strings are immutable.

Splitting on whitespace

The following statement splits the cleaned text:

String[] words = text.strip().split("\\p{javaWhitespace}+");

The regular expression has two parts:

Part Meaning
\p{javaWhitespace} Matches whitespace recognized by Character.isWhitespace()
+ Matches one or more consecutive whitespace characters

The backslash is written twice in the Java string literal so the regular expression receives a single backslash.

Using + treats consecutive spaces or tabs as one separator. This prevents empty elements between words when several whitespace characters appear together.

Reading the results

The enhanced for loop prints each element:

for (String word : words) {
    IO.println(word);
}

The array’s length field gives the number of resulting words.

Run the example

Compile and run the file:

javac SplitStringExample.java
java SplitStringExample

Output

Learn
Java
one
snippet
at
a
time.
Total words: 7

This example treats each whitespace-separated token as a word. Punctuation remains attached, so the last token is "time.". For language-aware word boundaries, use a text-processing tool such as BreakIterator.

How do I get started with 2D game programming in Java?

Game programming is a fun way to learn Java because it combines programming concepts with visual results. Instead of writing programs that only print text to the console, we can create characters, draw objects, and eventually build interactive games.

In this tutorial, we will take our first step into 2D game programming using Java Swing. We will create a simple game window and draw a player represented by a blue square.

This is the first article in our 2D Game Programming with Java Swing series, where we will learn to build games from scratch without relying on an external game engine.

What is 2D game programming?

A 2D game is a game where objects are positioned in a two-dimensional space using horizontal and vertical coordinates.

Some familiar examples include Pong, Snake, Breakout, and traditional platform games.

Most 2D games share several important concepts:

  • Game window: The area where the game is displayed.
  • Game objects: Players, enemies, obstacles, and other elements.
  • Rendering: Drawing game objects on the screen.
  • Input handling: Responding to keyboard and mouse input.
  • Game loop: Continuously updating and displaying the game.
  • Collision detection: Detecting when objects touch or overlap.

We will explore these concepts one at a time throughout the series.

Why use Java Swing?

Java Swing is a GUI toolkit included in the Java Development Kit (JDK). Although it was primarily designed for desktop applications, we can also use it to build simple 2D games.

Swing provides several useful classes:

Class Purpose
JFrame Creates the main game window
JPanel Provides an area for drawing game objects
Graphics Offers basic drawing operations
Graphics2D Provides more advanced 2D drawing capabilities
Timer Can schedule periodic updates for simple games

One advantage of using Swing is that we do not need to install an additional game engine.

By building games directly with Swing and Java 2D, we can better understand the fundamental techniques used in game development.

What do we need?

Before getting started, make sure you have:

  • Java Development Kit (JDK) 25
  • Apache Maven
  • A Java IDE such as IntelliJ IDEA, Eclipse, or NetBeans

We will use Java 25, an LTS release, for this series.

To verify your Java installation, open a terminal and run:

java -version

Then check your Maven installation:

mvn -version

Creating our first game project

Create a Maven project named java-2d-game.

Our initial project structure will look like this:

java-2d-game/
├── pom.xml
└── src/
    └── main/
        └── java/
            └── org/
                └── kodejava/
                    └── game/
                        └── Game.java

We will start with a single Java class to keep the example easy to understand.

As our game becomes more complex, we will introduce additional classes to organize the code.

Creating the game window

Let’s create a Java class named Game.java.

package org.kodejava.game;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;

public class Game extends JPanel {

    private static final int WIDTH = 800;
    private static final int HEIGHT = 600;

    public Game() {
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        setBackground(Color.BLACK);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g.create();

        try {
            g2d.setColor(Color.BLUE);
            g2d.fillRect(100, 100, 50, 50);
        } finally {
            g2d.dispose();
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("My First Java 2D Game");

            frame.setDefaultCloseOperation(
                    JFrame.EXIT_ON_CLOSE);

            frame.add(new Game());
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setResizable(false);
            frame.setVisible(true);
        });
    }
}

When we run this program, a window will appear with a black background and a blue square.

The square represents our first game object. It does not move yet, but it provides a starting point for the next tutorials.

Game Window

Game Window

Understanding the code

Let’s examine the important parts of our program.

Extending JPanel

public class Game extends JPanel {

By extending JPanel, our class becomes a Swing component that can display custom graphics.

We can override its painting method to draw our game objects.

Setting the game area

setPreferredSize(new Dimension(800, 600));
setBackground(Color.BLACK);

Here, we define a preferred game area of 800 pixels wide and 600 pixels high.

We also set the background color to black.

Drawing the player

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);

    Graphics2D g2d = (Graphics2D) g.create();

    try {
        g2d.setColor(Color.BLUE);
        g2d.fillRect(100, 100, 50, 50);
    } finally {
        g2d.dispose();
    }
}

The paintComponent() method is called by Swing when the component needs to be painted.

We call super.paintComponent(g) first so that Swing can perform its standard component painting.

Next, we create a copy of the graphics context and use Graphics2D to draw a rectangle.

The fillRect() method accepts four parameters:

fillRect(x, y, width, height);

In our example:

  • x = 100: The horizontal position.
  • y = 100: The vertical position.
  • width = 50: The rectangle width.
  • height = 50: The rectangle height.

The rectangle is drawn at position (100, 100) and has a size of 50 × 50 pixels.

Finally, we dispose of the copied graphics context to release its associated resources.

Creating the main window

JFrame frame = new JFrame("My First Java 2D Game");

The JFrame class creates the main desktop window.

We then add our game panel:

frame.add(new Game());

The pack() method adjusts the window size based on the preferred size of its contents.

We use setLocationRelativeTo(null) to center the window on the screen.

Understanding SwingUtilities.invokeLater()

SwingUtilities.invokeLater(() -> {
    // Create and display the game window
});

Swing uses a special thread called the Event Dispatch Thread (EDT) to process GUI events and update components.

Using SwingUtilities.invokeLater() ensures that our Swing interface is created on this thread.

This is important because most Swing components are not thread-safe.

Later in this series, we will learn how to coordinate game updates with Swing rendering.

Running the program

First, compile the project using Maven:

mvn compile

Then run the main class from your IDE.

Alternatively, after compiling, you can run it from the project root using:

java -cp target/classes org.kodejava.game.Game

You should see an 800 × 600 game area with a blue square displayed near the upper-left corner.

Congratulations! You have created your first 2D game window in Java.

Although the square is not interactive yet, we already have two essential components of a game: a window and a rendering area.

Try it yourself

Before moving to the next tutorial, try making a few changes to the program.

Exercise 1: Change the player color

Replace:

g2d.setColor(Color.BLUE);

with:

g2d.setColor(Color.GREEN);

Exercise 2: Move the player

Modify the rectangle position:

g2d.fillRect(300, 200, 50, 50);

Observe how the square appears at a different location.

Exercise 3: Change the player size

Try drawing a larger rectangle:

g2d.fillRect(100, 100, 100, 100);

These simple exercises introduce coordinates, colors, and object dimensions, which are fundamental concepts in 2D game programming.

Summary

In this tutorial, we learned how to create our first 2D game window using Java Swing.

We covered:

  • The basic concepts of 2D game programming.
  • Why Java Swing is useful for learning game development.
  • How to create a game window using JFrame.
  • How to create a drawing area using JPanel.
  • How to draw a rectangle using Graphics2D.
  • Why Swing applications use the Event Dispatch Thread.

In the next tutorial, How do I create a game window using Java Swing?, we will explore game window configuration in greater detail and separate our main window from the drawing panel.

Maven Dependencies

Java Swing and Java 2D are included in the standard JDK through the java.desktop module, so no external dependencies are required.

We only need to configure Maven to compile our project with Java 25.

Here is the complete pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="
             http://maven.apache.org/POM/4.0.0
             https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>org.kodejava</groupId>
    <artifactId>java-2d-game</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.release>25</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.14.1</version>
            </plugin>
        </plugins>
    </build>
</project>

Required Maven artifacts: None for Swing or Java 2D.

The Maven Compiler Plugin is configured as a build plugin, not an application dependency.

Java API references: