Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

A Java game loop repeatedly processes input, advances the game state, draws the current state, regulates timing, and shuts down cleanly. For a plain desktop prototype, start with a hand-written Runnable loop and measure elapsed time with System.nanoTime(). If you are using Swing, JavaFX, libGDX, or another toolkit, first check whether it already owns the outer loop.

Use a variable timestep for simple movement and prototypes. Use a fixed timestep when physics, replays, networking, or reproducible simulation matter.

What a game loop does

The loop is the recurring heartbeat of a game:

while (game is running) {
    process input
    update simulation
    render
    regulate timing
}

These responsibilities are related but not identical:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Input processing captures keyboard, mouse, controller, or window events and turns them into input state or commands.
  • Updating changes positions, velocities, enemies, timers, animations, collisions, and game rules.
  • Rendering draws the current or interpolated game state. It should not decide gameplay rules.
  • Timing determines how much simulated time passes between updates.
  • Lifecycle management starts, pauses, resumes, and stops the loop and releases resources.

Frameworks often hide the outer while statement. For example, libGDX invokes ApplicationListener.render() as the body of its framework-managed loop rather than asking application code to create another loop.

#1 Best Overall
8Bitdo Ultimate 2C Wireless Controller for Windows PC and Android, with 1000 Hz Polling Rate, Hall Effect Joysticks and Triggers, and Remappable L4/R4 Bumpers (Green)
  • Compatible with Windows and Android.
  • 1000Hz Polling Rate (for 2.4G and wired connection)
  • Hall Effect joysticks and Hall triggers. Wear-resistant metal joystick rings.
  • Extra R4/L4 bumpers. Custom button mapping without using software. Turbo function.
  • Refined bumpers and D-pad. Light but tactile.

Start with a graphics-free loop

Build the timing and update architecture before adding a window or renderer. This makes the most important rule obvious: movement must be expressed in units per second, not units per frame.

public final class Main {
    public static void main(String[] args) {
        Game game = new Game();
        Thread gameThread = new Thread(game, "game-loop");
        gameThread.start();
    }
}

final class Game implements Runnable {
    private volatile boolean running = true;
    private double playerX = 100.0;

    @Override
    public void run() {
        long previous = System.nanoTime();

        while (running) {
            long now = System.nanoTime();
            double deltaSeconds =
                    (now - previous) / 1_000_000_000.0;
            previous = now;

            update(deltaSeconds);
            render();
        }
    }

    private void update(double deltaSeconds) {
        double speedPixelsPerSecond = 200.0;
        playerX += speedPixelsPerSecond * deltaSeconds;
    }

    private void render() {
        // Add graphics later.
    }

    public void stop() {
        running = false;
    }
}

A loop that only does playerX += 5 moves farther on a high-refresh or fast computer because the statement executes more often. The time-based form, position += speed * deltaSeconds, keeps movement approximately consistent as the frame rate changes.

This first version is intentionally incomplete. Without rendering, event pumping, frame pacing, and resource cleanup, it can consume an entire CPU core and is not yet a production game loop.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Measure elapsed time with System.nanoTime()

Use System.nanoTime() for elapsed-time calculations:

long now = System.nanoTime();
double seconds =
        (now - previous) / 1_000_000_000.0;

Its value has an arbitrary origin and no calendar meaning. Compare two readings from the same JVM and use their difference. The API is intended for elapsed-time measurement, but nanosecond units do not guarantee nanosecond-level accuracy or resolution; precision, resolution, and accuracy are different properties. See the Java System API documentation.

Do not use System.currentTimeMillis() for the core loop unless you have a specific reason. It represents wall-clock time, which can be adjusted, and generally has coarser, platform-dependent granularity.

Always guard against an unusually large delta. A breakpoint, window drag, laptop sleep, or operating-system stall can make the next measured interval enormous:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Sale
XBOX Wireless Gaming Controller | Shock Blue | Consoles, PCs, TVs, mobile, and more | Textured Grip | Wireless, Bluetooth, USB-C Connectivity
  • MODERNIZED DESIGN — Experience the modernized design of the XBOX Wireless Controller with sculpted surfaces and updated geometry that enhances comfort and control during long gaming sessions.
  • PRECISION PERFORMANCE — Stay on target with a hybrid D-pad and textured grips on triggers, bumpers, and back case for improved accuracy and handling.
  • SHARE BUTTON: Seamlessly capture and share content such as screenshots, recordings, and more with the new Share button.
  • VERSATILE CONNECTIVITY — Connect via USB-C for plug-and-play on console and PC, or quickly pair and switch between supported devices with XBOX Wireless and Bluetooth support.
  • BUILT-IN AUDIO SUPPORT — Plug in compatible headsets using the 3.5mm audio jack for direct voice chat and immersive in-game sound.
deltaSeconds = Math.min(deltaSeconds, 0.25);

The value 0.25 seconds is a design safeguard, not a Java requirement. Values around 0.1 to 0.25 seconds are common choices.

Build a frame-rate-independent variable-timestep loop

In a variable-timestep loop, each update receives the time since the previous iteration:

update(deltaSeconds);

This approach is simple and often sufficient for menus, UI animation, basic movement, and prototypes. Its weakness is that physics and collision code can behave differently when supplied with different delta sizes. A large update may also let a fast-moving object tunnel through an obstacle.

A practical plain-Java version is:

public final class GameLoop implements Runnable {
    private static final double MAX_DELTA_SECONDS = 0.25;
    private volatile boolean running = true;

    @Override
    public void run() {
        initialize();
        long previousTime = System.nanoTime();

        try {
            while (running) {
                long currentTime = System.nanoTime();
                double deltaSeconds =
                        (currentTime - previousTime)
                                / 1_000_000_000.0;
                previousTime = currentTime;

                deltaSeconds = Math.min(
                        deltaSeconds, MAX_DELTA_SECONDS);

                processInput();
                update(deltaSeconds);
                render();
            }
        } finally {
            dispose();
        }
    }

    public void stop() {
        running = false;
    }

    private void initialize() { }
    private void processInput() { }
    private void update(double deltaSeconds) { }
    private void render() { }
    private void dispose() { }
}

Prevent the loop from consuming a CPU core

An uncapped loop runs as quickly as the machine allows. That may reduce input latency, but it wastes CPU and can generate unnecessary heat when the renderer does not need more frames.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A coarse limiter can sleep for the unused part of a frame budget:

long frameBudgetNanos = 1_000_000_000L / 60L;
long frameStart = System.nanoTime();

// update and render

long elapsed = System.nanoTime() - frameStart;
long remaining = frameBudgetNanos - elapsed;

if (remaining > 0) {
    try {
        Thread.sleep(
                remaining / 1_000_000L,
                (int) (remaining % 1_000_000L));
    } catch (InterruptedException exception) {
        Thread.currentThread().interrupt();
        running = false;
    }
}

Sleeping for approximately one-sixtieth of a second does not guarantee exactly 60 frames per second. The actual duration depends on the operating system timer and scheduler. Java documents this limitation in the Thread.sleep() API.

A more precise limiter sleeps for most of the remaining time and then uses a short final spin or yield, but that increases CPU usage and complexity. For graphics applications, v-sync or a framework-managed render cadence is generally preferable. Also, a 60-Hz simulation, a 60-FPS renderer, and a display refreshing at 60 Hz are separate concepts.

Rank #3
Sale
Logitech G F310 Wired Gamepad Controller Console - Blue/Black
  • With broad game support, the Logitech Gamepad F310 works with old standbys to today's biggest titles, so it's easy to set up and use with your favorite games.
  • Profiler software allows the gamepad to be programmed to perform keyboard and mouse commands for games without gamepad support.* * Requires software installation.
  • A familiar control layout that doesn't require a learning curve to be able to use, with all the same buttons as on an Xbox 360.
  • The unique floating D-pad rests on four switches-instead of a single pivot point-making it responsive to quick changes in direction.
  • The six-foot cord lets you lean back and play a comfortable distance from your PC monitor.

Use a fixed timestep for physics and simulation

A fixed-timestep loop advances the simulation by a constant amount, such as 1.0 / 60.0 seconds, even when rendering occurs at a different rate. An accumulator stores real time that has not yet been simulated.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
public final class FixedTimestepLoop implements Runnable {
    private static final double UPDATE_RATE = 60.0;
    private static final double FIXED_DELTA = 1.0 / UPDATE_RATE;
    private static final double MAX_FRAME_TIME = 0.25;
    private static final int MAX_UPDATES_PER_FRAME = 5;

    private volatile boolean running = true;
    private double accumulator;

    @Override
    public void run() {
        long previousTime = System.nanoTime();
        initialize();

        try {
            while (running) {
                long currentTime = System.nanoTime();
                double frameTime =
                        (currentTime - previousTime)
                                / 1_000_000_000.0;
                previousTime = currentTime;

                frameTime = Math.min(frameTime, MAX_FRAME_TIME);
                accumulator += frameTime;

                processInput();

                int updates = 0;
                while (accumulator >= FIXED_DELTA
                        && updates < MAX_UPDATES_PER_FRAME) {
                    update(FIXED_DELTA);
                    accumulator -= FIXED_DELTA;
                    updates++;
                }

                render(accumulator / FIXED_DELTA);
                limitCpuUsage();
            }
        } finally {
            dispose();
        }
    }

    public void stop() {
        running = false;
    }

    private void initialize() { }
    private void processInput() { }
    private void update(double deltaSeconds) { }
    private void render(double interpolation) { }
    private void dispose() { }

    private void limitCpuUsage() {
        try {
            Thread.sleep(1);
        } catch (InterruptedException exception) {
            Thread.currentThread().interrupt();
            running = false;
        }
    }
}

At 60 Hz, the fixed update interval is approximately 16.67 milliseconds. That is a conventional starting point, not a universal requirement. A 30-Hz simulation uses 1.0 / 30.0. Choose based on the game’s physics and performance budget.

The spiral of death

If updates take longer to calculate than the simulated time they represent, the accumulator grows:

simulation falls behind → more updates are required → the frame takes longer → the simulation falls further behind

The maximum frame time and maximum updates per frame are fail-safes. If the cap is reached, the loop must leave some accumulated time unprocessed. That prevents a permanent lockup, but the simulation may temporarily slow down or effectively skip recovery after a severe stall. Other choices include reducing simulation complexity, pausing while the application is inactive, or displaying a diagnostic warning.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A fixed timestep improves repeatability, but it does not automatically guarantee determinism. Deterministic results also require controlled random numbers, stable input ordering, deterministic game logic and collections, no unsynchronized races, and care with platform-dependent floating-point behavior.

Smooth fixed-timestep rendering with interpolation

When simulation runs at 60 Hz and rendering runs at 144 Hz, the renderer often executes between two simulation states. Keep the authoritative current state and the previous state, then interpolate only for display:

Rank #4
MYSTILUCK Wireless Pro Controller for Switch/Switch 2/Lite/OLED/PC
  • Compatible with Wide Range of Consoles: This controller works with consoles such as Switch 2, Switch, Switch Pro, Switch Lite, and Switch OLED. (Please note): The controller's “HOME” button cannot wake up the Switch 2 console and does not have the C button for voice chat functions. However, all other functions are fully usable, including: dual vibration, 6-axis gyroscope, screenshot function, Hall effect buttons, and turbo.
  • Cool and Colorful Lighting Switch Controller Wireless: It features 7 colors of RGB lighting (Red - Orange - Yellow - Green - Cyan - Blue - Violet) and 4 light modes (Dazzle - Monochrome - Monochrome Breathe - Monochrome Breathe Cycle).
  • Hall Effect Technology for Switch Pro Controller: Experience zero drift and unmatched accuracy with our Hall effect joystick switch. Adaptive trigger feedback with adjustable resistance levels lets you feel every action. With <0.1 ms response time and 256 levels of pressure sensitivity, enjoy instant trigger detection in FPS games. 3+ million clicks on the controller mean a long service life.
  • Dual Motor Vibration, Turbo Function and 6 Axis Gyroscope: The switch 2 controller has two vibration motors with three intensity levels—off, low, and high—and provides exceptional haptic feedback to enhance the gaming experience. The controller also offers three adjustable turbo speeds (5-10-15 Hz), which are particularly suitable for first-person shooter games. In addition, it features a 6 axis gyroscope chip for precise motion control. The physical movements of the players are precisely matched to the actions of their game characters.
  • Reliable After-Sales Support You Can Count On: Your satisfaction is our top priority. Should you experience any quality concerns with your gaming controller, simply reach out to us via our customer service email, and we’ll respond promptly. We stand behind our product with a hassle-free replacement policy—ensuring you’re back to gaming without worry, no questions asked.
double alpha = accumulator / FIXED_DELTA;
double renderedX =
        previousX + (currentX - previousX) * alpha;

Before each fixed update, copy the current position into previousX; then advance the authoritative state. Render renderedX, not a modified gameplay position. Input, collisions, and rules must use the simulation state. Interpolation is a visual bridge and should not change the simulation.

Capture input without coupling gameplay to callbacks

Windowing toolkits receive events asynchronously from the game’s point of view. A key listener or mouse callback should usually record state or enqueue a command rather than running expensive gameplay logic immediately:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
private volatile boolean moveLeft;
private volatile boolean moveRight;

private void update(double deltaSeconds) {
    if (moveLeft) {
        playerX -= speed * deltaSeconds;
    }
    if (moveRight) {
        playerX += speed * deltaSeconds;
    }
}

For a larger game, use an input queue and consume commands during the update step. This gives events a predictable order. Capture input promptly, but apply it during the next simulation update. Sampling only once per fixed tick can otherwise make input feel delayed. Avoid performing file I/O, pathfinding, networking, or other long-running work inside an event callback or render callback.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Choosing the right Java loop

Approach Best for Main limitation
Hand-written while loop Learning, prototypes, custom engines, low-level rendering You own timing, events, shutdown, and threading
Swing Timer Simple Swing animation and board games Callbacks run on the Event Dispatch Thread
JavaFX AnimationTimer JavaFX games, canvas projects, and simulations Callbacks run on the JavaFX Application Thread
libGDX render() Cross-platform Java games The framework owns the outer loop
LWJGL with GLFW Low-level OpenGL/Vulkan-style control You own much of the window, event, rendering, and lifecycle architecture
ScheduledExecutorService Server ticks and periodic background work It is not inherently a graphics-rendering loop

Swing

A repeating javax.swing.Timer is suitable for simple GUI animation. Its action handlers execute on Swing’s Event Dispatch Thread, so keep them short and never block that thread. Render through a Swing component’s paintComponent. A separate simulation thread is possible, but it requires careful synchronization and correct handoff to the UI thread. See Oracle’s Swing timer documentation.

JavaFX

JavaFX already supplies a per-frame callback through AnimationTimer:

AnimationTimer timer = new AnimationTimer() {
    private long previous = -1;

    @Override
    public void handle(long now) {
        if (previous < 0) {
            previous = now;
            return;
        }

        double delta = (now - previous)
                / 1_000_000_000.0;
        previous = now;

        delta = Math.min(delta, 0.25);
        update(delta);
        render();
    }
};

timer.start();

handle(long now) runs once per frame while active on the JavaFX Application Thread. Do not put blocking file operations, network calls, or expensive pathfinding in it. Use worker threads for such work and marshal scene-graph changes back to the JavaFX thread. See the JavaFX AnimationTimer API.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

libGDX

In libGDX, implement the framework callback rather than creating another outer loop:

Best Value
PlayStation DualSense™ Wireless Controller – Galactic Purple - For PS5, PC, MAC & Mobile
  • Feel physically responsive feedback to your in-game actions through haptic feedback
  • Experience varying levels of force and tension at your fingertips with adaptive triggers
  • Chat online through the built-in microphone and connect a headset directly through the 3.5mm jack
  • Switch voice capture on and off using the dedicated mute button
  • Play on more devices using the USB Type-C cable or Bluetooth to connect easily to Windows PC and Mac computers, Android and iOS mobile phones as well as your PlayStation 5
@Override
public void render() {
    float delta = Gdx.graphics.getDeltaTime();
    update(delta);

    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    renderWorld();
}

Gdx.graphics.getDeltaTime() supplies elapsed time for typical updates. Application-listener methods normally run on the rendering thread, which is also the normal thread for OpenGL operations. Use worker threads for background work, then synchronize or post results safely to the rendering thread. Read libGDX’s lifecycle, simple game, and threading documentation.

LWJGL and GLFW

LWJGL provides low-level Java bindings; it does not supply a complete game engine. A typical GLFW loop is:

while (!glfwWindowShouldClose(window)) {
    glfwPollEvents();
    // calculate elapsed time
    // update input and game state
    // render
    glfwSwapBuffers(window);
}

glfwPollEvents() must be called regularly or the window can stop responding. glfwSwapBuffers(window) presents the completed frame. GLFW leaves ownership of the main loop to the application, so you must design lifecycle, timing, synchronization, and cleanup yourself. The current LWJGL getting-started guide lists Java 8 or later as a requirement; verify the guide for the version you install.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

ScheduledExecutorService

A scheduled executor can run periodic tasks:

ScheduledExecutorService executor =
        Executors.newSingleThreadScheduledExecutor();

ScheduledFuture<?> task = executor.scheduleAtFixedRate(
        () -> update(FIXED_DELTA),
        0,
        16,
        TimeUnit.MILLISECONDS);

scheduleAtFixedRate schedules executions relative to a planned schedule; scheduleWithFixedDelay waits for a delay after one execution finishes. This is useful for server ticks and background jobs, but it does not solve graphics-thread affinity, rendering cadence, overruns, exceptions, synchronization, or shutdown. Cancel the returned future and shut down the executor when the application ends. See the Java scheduling API.

Threading and shutdown

Do not update Swing components, JavaFX scene-graph objects, or OpenGL resources from an arbitrary thread. If update and render run on different threads, shared mutable state needs a deliberate synchronization design, such as immutable snapshots, a command queue, or synchronized access.

Use a visible shutdown path and release resources in finally or the framework’s lifecycle method:

game.stop();
try {
    gameThread.join();
} catch (InterruptedException exception) {
    Thread.currentThread().interrupt();
}

A volatile running flag lets another thread request termination. If the loop is blocked in a wait or sleep, interruption may also be necessary. Restore the interrupt flag after catching InterruptedException.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A dedicated loop can be started with a shutdown hook:

Runtime.getRuntime().addShutdownHook(
        new Thread(game::stop, "game-shutdown"));

This requests shutdown; resource ownership and thread termination still belong to the application.

Debug timing instead of guessing

Display or log:

  • FPS and frame time
  • Update time and render time separately
  • The number of fixed updates performed per rendered frame
  • Accumulator size
  • Large-delta events and capped or dropped updates
  • Allocation rates and garbage-collection pauses

Test at different window sizes, refresh rates, and machine speeds. Also test pause/resume, minimizing the window, dragging it, closing it during loading, and recovering from a breakpoint. These cases expose timing and lifecycle bugs that a steady desktop run can hide.

Quick Recap

Bestseller No. 1
8Bitdo Ultimate 2C Wireless Controller for Windows PC and Android, with 1000 Hz Polling Rate, Hall Effect Joysticks and Triggers, and Remappable L4/R4 Bumpers (Green)
8Bitdo Ultimate 2C Wireless Controller for Windows PC and Android, with 1000 Hz Polling Rate, Hall Effect Joysticks and Triggers, and Remappable L4/R4 Bumpers (Green)
Compatible with Windows and Android.; 1000Hz Polling Rate (for 2.4G and wired connection); Hall Effect joysticks and Hall triggers. Wear-resistant metal joystick rings.
$29.99
SaleBestseller No. 3
Logitech G F310 Wired Gamepad Controller Console - Blue/Black
Logitech G F310 Wired Gamepad Controller Console - Blue/Black
The six-foot cord lets you lean back and play a comfortable distance from your PC monitor.
$15.99
Bestseller No. 5
PlayStation DualSense™ Wireless Controller – Galactic Purple - For PS5, PC, MAC & Mobile
PlayStation DualSense™ Wireless Controller – Galactic Purple - For PS5, PC, MAC & Mobile
Feel physically responsive feedback to your in-game actions through haptic feedback; Experience varying levels of force and tension at your fingertips with adaptive triggers
$74.00

A practical decision rule

  1. Use a framework-managed callback when your toolkit owns the window or graphics lifecycle.
  2. Use a variable timestep for straightforward movement, UI animation, and early prototypes.
  3. Use a fixed timestep for collision-heavy physics, deterministic tests, replays, or network simulation.
  4. Use System.nanoTime() for elapsed time and clamp unusually large intervals.
  5. Keep input capture, simulation, and rendering separate even when they execute on one thread.
  6. Give every loop an explicit stop, interruption, and resource-disposal path.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.