Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/LWJGL/lwjgl3/llms.txt

Use this file to discover all available pages before exploring further.

GLFW is the windowing and input layer that most LWJGL 3 applications use. It handles OS window creation, OpenGL context setup, keyboard and mouse events, and the swap-buffer loop across Windows, macOS, and Linux. This guide walks through every step from glfwInit to glfwTerminate, using patterns drawn directly from the LWJGL sample suite.
1

Initialize GLFW and install an error callback

Always install an error callback before calling any other GLFW function. If GLFW encounters a problem it will invoke the callback rather than silently failing.
import static org.lwjgl.glfw.GLFW.*;
import org.lwjgl.glfw.GLFWErrorCallback;

// Print GLFW errors to stderr
GLFWErrorCallback.createPrint(System.err).set();

if (!glfwInit()) {
    throw new IllegalStateException("Unable to initialize GLFW");
}
GLFWErrorCallback.createPrint(System.err) allocates a native callback that forwards every error to System.err. You must free it on shutdown (see the cleanup step).
glfwInit() returns false if the platform or environment doesn’t support GLFW. Always check the return value.
2

Configure window hints

Window hints tell GLFW what kind of window and context to create. Set them after glfwInit but before glfwCreateWindow.
glfwDefaultWindowHints();                          // reset to defaults
glfwWindowHint(GLFW_VISIBLE,   GLFW_FALSE);        // hidden until ready
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);  // HiDPI aware

// Request an OpenGL debug context for development builds
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);

// macOS: avoid Retina scaling at the OpenGL level
if (glfwGetPlatform() == GLFW_PLATFORM_COCOA) {
    glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE);
}
Common hints for OpenGL version targeting:
HintPurpose
GLFW_CONTEXT_VERSION_MAJOR / MINORMinimum OpenGL version (e.g., 3, 3)
GLFW_OPENGL_PROFILEGLFW_OPENGL_CORE_PROFILE for core profile
GLFW_OPENGL_FORWARD_COMPATRequired on macOS for core profile
GLFW_OPENGL_DEBUG_CONTEXTEnables the OpenGL debug message extension
Call glfwDefaultWindowHints() at the start of each window-creation block to ensure no stale hints from a previous configuration interfere.
3

Create a window

glfwCreateWindow returns a long handle — LWJGL represents all native pointers as long. A return value of NULL (0L) means creation failed.
import static org.lwjgl.system.MemoryUtil.*;

int WIDTH  = 800;
int HEIGHT = 600;

long window = glfwCreateWindow(WIDTH, HEIGHT, "My LWJGL App", NULL, NULL);
if (window == NULL) {
    throw new RuntimeException("Failed to create the GLFW window");
}

// Center on the primary monitor
GLFWVidMode vidmode = Objects.requireNonNull(
    glfwGetVideoMode(glfwGetPrimaryMonitor())
);
glfwSetWindowPos(
    window,
    (vidmode.width()  - WIDTH)  / 2,
    (vidmode.height() - HEIGHT) / 2
);
The third argument is the window title, and the fourth is a monitor handle for fullscreen mode — pass NULL for windowed mode. The fifth argument is for context sharing between windows.
4

Register callbacks

GLFW delivers input and window events through callbacks you register once. LWJGL represents each callback as a functional interface backed by a native function pointer.
// Close on Escape, toggle fullscreen on F
glfwSetKeyCallback(window, (win, key, scancode, action, mods) -> {
    if (action == GLFW_RELEASE) {
        if (key == GLFW_KEY_ESCAPE) {
            glfwSetWindowShouldClose(win, true);
        }
    }
});

// React to window resize — update the viewport
glfwSetFramebufferSizeCallback(window, (win, width, height) -> {
    if (width > 0 && height > 0) {
        // e.g., call glViewport(0, 0, width, height) here
    }
});

// Redraw immediately when the OS exposes the window
glfwSetWindowRefreshCallback(window, win -> {
    render();
    glfwSwapBuffers(win);
});
Available callback types include key, character, mouse button, cursor position, cursor enter/leave, scroll, drop, window position, size, close, focus, iconify, maximize, content scale, and framebuffer size. See Events.java in the LWJGL samples for a demonstration of all of them.
Every lambda you pass to a glfwSet*Callback function allocates a native object. Use Callbacks.glfwFreeCallbacks(window) before destroying the window to release all of them at once.
5

Make the OpenGL context current

Before you can call any OpenGL function you must bind the GLFW window’s context to the calling thread, then create LWJGL’s capabilities wrapper.
import org.lwjgl.opengl.GL;

glfwMakeContextCurrent(window);

// Build the GLCapabilities object — required before any GL call
GL.createCapabilities();
GL.createCapabilities() queries the driver for all supported extensions and OpenGL versions. The returned GLCapabilities object is stored per-thread and consulted whenever you call an OpenGL function.
6

Run the main loop

The main loop polls events, renders a frame, and swaps buffers until the user signals the window should close.
// Enable vsync: swap buffers once per monitor refresh
glfwSwapInterval(1);

glfwShowWindow(window);  // make the window visible now that GL is ready

while (!glfwWindowShouldClose(window)) {
    glfwPollEvents();   // dispatch pending events to your callbacks

    // --- render your scene here ---
    render();
    // ------------------------------

    glfwSwapBuffers(window);
}
glfwSwapInterval(1) enables vsync by synchronising buffer swaps to the monitor refresh rate, which prevents screen tearing. Use 0 to disable it.
If your window only needs to redraw in response to user input (e.g., a GUI tool), replace glfwPollEvents() with glfwWaitEvents() to block the thread and save CPU time when there is nothing to do.
7

Clean up

Release all resources in reverse order of acquisition. LWJGL callbacks are native allocations — failing to free them is a memory leak.
import static org.lwjgl.glfw.Callbacks.glfwFreeCallbacks;

// 1. Free all window callbacks registered with glfwSet*Callback
glfwFreeCallbacks(window);

// 2. Destroy the window and its OpenGL context
glfwDestroyWindow(window);

// 3. Terminate GLFW and free the error callback
glfwTerminate();
Objects.requireNonNull(glfwSetErrorCallback(null)).free();
glfwFreeCallbacks (from org.lwjgl.glfw.Callbacks) iterates every window-level callback slot, resets each one to NULL, and calls Callback.free() on the previous value. Non-window callbacks such as the error callback and monitor callback must be freed separately.

Putting it together

The following is the minimal skeleton of a working GLFW application:
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;
import java.util.Objects;

import static org.lwjgl.glfw.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11C.*;
import static org.lwjgl.system.MemoryUtil.*;

public class App {
    private long window;

    public void run() {
        init();
        loop();
        cleanup();
    }

    private void init() {
        GLFWErrorCallback.createPrint(System.err).set();
        if (!glfwInit()) throw new IllegalStateException("Unable to initialize GLFW");

        glfwDefaultWindowHints();
        glfwWindowHint(GLFW_VISIBLE,   GLFW_FALSE);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);

        window = glfwCreateWindow(800, 600, "My App", NULL, NULL);
        if (window == NULL) throw new RuntimeException("Failed to create the GLFW window");

        glfwSetKeyCallback(window, (win, key, scancode, action, mods) -> {
            if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
                glfwSetWindowShouldClose(win, true);
            }
        });

        glfwMakeContextCurrent(window);
        GL.createCapabilities();
        glfwSwapInterval(1);
        glfwShowWindow(window);
    }

    private void loop() {
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        while (!glfwWindowShouldClose(window)) {
            glfwPollEvents();
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            // render scene
            glfwSwapBuffers(window);
        }
    }

    private void cleanup() {
        glfwFreeCallbacks(window);
        glfwDestroyWindow(window);
        glfwTerminate();
        Objects.requireNonNull(glfwSetErrorCallback(null)).free();
    }

    public static void main(String[] args) {
        new App().run();
    }
}

Next steps

Build docs developers (and LLMs) love