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.

LWJGL 3 exposes the entire OpenGL API as static Java methods, mirroring the C function names one-to-one. There is no object wrapper around a context — you work directly with integer handles for programs, shaders, buffers, and vertex arrays, exactly as you would in C. This guide starts from a working GLFW window (see Create Windows and Handle Input with GLFW) and walks through the additional OpenGL steps needed to draw geometry on screen.
LWJGL wraps OpenGL faithfully but does not define its semantics. Once you understand the LWJGL call patterns shown here, the OpenGL wiki and the official specification are the authoritative references for what each function does.

The OpenGL class hierarchy

LWJGL splits the OpenGL API across version-named classes. Import from the most specific class your application targets:
ClassCovers
org.lwjgl.opengl.GL11COpenGL 1.1 core (glClear, glDrawArrays, …)
org.lwjgl.opengl.GL15COpenGL 1.5 (glGenBuffers, glBindBuffer, glBufferData, …)
org.lwjgl.opengl.GL20COpenGL 2.0 (glCreateShader, glCompileShader, glUseProgram, …)
org.lwjgl.opengl.GL30COpenGL 3.0 (glGenVertexArrays, glBindVertexArray, …)
org.lwjgl.opengl.GLCapabilities bootstrap (GL.createCapabilities())
The C-suffixed classes contain only core-profile functions. The non-C classes add compatibility-profile functions. Use the C variants for new code.
// Wildcard import covers GL 1.1 – 3.0+ core profile
import static org.lwjgl.opengl.GL30C.*;
1

Create an OpenGL context via GLFW

Follow the windowing guide to create a GLFW window. Before making any OpenGL call, bind the context and initialise LWJGL’s capabilities:
glfwMakeContextCurrent(window);
GL.createCapabilities();
GL.createCapabilities() is mandatory. It probes the driver, populates a thread-local GLCapabilities object, and sets up all the function pointers that LWJGL will use. Call it exactly once per thread that will issue GL commands.
2

Install a debug message callback

During development, enable the OpenGL debug message extension so driver errors are reported immediately rather than surfacing as silent misbehaviour later.
import org.lwjgl.opengl.GLUtil;
import org.lwjgl.system.Callback;

// Returns a Callback that must be freed on shutdown
Callback debugProc = GLUtil.setupDebugMessageCallback();
GLUtil.setupDebugMessageCallback() checks for GL_KHR_debug or GL_ARB_debug_output, installs a callback that prints every message to System.err, and returns the callback object so you can free its native memory when you are done.
The debug callback is only triggered when the context was created with GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE. Without that window hint this call is a no-op.
3

Clear the screen

Set the background colour once and clear the framebuffer at the start of every frame:
// Set the clear colour — call once, or whenever the background should change
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);  // dark grey, fully opaque

// In the render loop: clear colour and depth buffers
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
GL_COLOR_BUFFER_BIT fills the colour attachment with the clear colour. GL_DEPTH_BUFFER_BIT resets depth values to 1.0 so depth testing works correctly for the new frame. This pattern is used in every LWJGL OpenGL sample.
4

Upload vertex data with a VAO and VBO

Modern OpenGL stores geometry in GPU-side Vertex Buffer Objects (VBOs) and records the layout in a Vertex Array Object (VAO). The GLXGears sample demonstrates both:
import org.lwjgl.BufferUtils;
import java.nio.FloatBuffer;

// --- geometry ---
float[] vertices = {
    // X      Y     Z
     0.0f,  0.5f, 0.0f,   // top
    -0.5f, -0.5f, 0.0f,   // bottom-left
     0.5f, -0.5f, 0.0f    // bottom-right
};

FloatBuffer vertexBuffer = BufferUtils.createFloatBuffer(vertices.length);
vertexBuffer.put(vertices).flip();

// --- VAO ---
int vao = glGenVertexArrays();
glBindVertexArray(vao);

// --- VBO ---
int vbo = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, vertexBuffer, GL_STATIC_DRAW);

// Describe how the vertex shader should read attribute 0
int positionAttrib = 0;
glVertexAttribPointer(
    positionAttrib, // attribute index
    3,              // number of components (x, y, z)
    GL_FLOAT,       // component type
    false,          // normalise?
    0,              // stride — 0 = tightly packed
    0L              // byte offset into the VBO
);
glEnableVertexAttribArray(positionAttrib);
GL_STATIC_DRAW tells the driver the data will be uploaded once and drawn many times. Use GL_DYNAMIC_DRAW for geometry that changes every frame.
5

Compile and link GLSL shaders

The following pattern is used in GLXGears.java to compile a vertex shader, a fragment shader, and link them into a program:
// Compile a single shader stage
private static int compileShader(int type, String source) {
    int shader = glCreateShader(type);
    glShaderSource(shader, source);
    glCompileShader(shader);

    if (glGetShaderi(shader, GL_COMPILE_STATUS) != GL_TRUE) {
        throw new IllegalStateException(
            "Shader compile error:\n" + glGetShaderInfoLog(shader)
        );
    }
    return shader;
}

// Link vertex and fragment shaders into a program
private static int linkProgram(int vs, int fs) {
    int program = glCreateProgram();
    glAttachShader(program, vs);
    glAttachShader(program, fs);
    glLinkProgram(program);

    if (glGetProgrami(program, GL_LINK_STATUS) != GL_TRUE) {
        throw new IllegalStateException(
            "Program link error:\n" + glGetProgramInfoLog(program)
        );
    }
    glUseProgram(program);
    return program;
}
Minimal GLSL shaders for the triangle above:
#version 330 core
layout(location = 0) in vec3 in_Position;

void main() {
    gl_Position = vec4(in_Position, 1.0);
}
Compile and link:
int vs      = compileShader(GL_VERTEX_SHADER,   vertexSource);
int fs      = compileShader(GL_FRAGMENT_SHADER, fragmentSource);
int program = linkProgram(vs, fs);
6

Draw the triangle and swap buffers

With the VAO bound and the program active, a single glDrawArrays call sends the triangle to the GPU:
// In the render loop, after glClear:
glUseProgram(program);
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES, 0, 3);  // 3 vertices, starting at index 0

// Hand the completed frame to the display
glfwSwapBuffers(window);
glfwSwapBuffers exchanges the back buffer (where you just rendered) with the front buffer (what the monitor is currently showing).

Checking driver support at runtime

Use the GLCapabilities object returned (or accessible via GL.getCapabilities()) to branch on optional features:
GLCapabilities caps = GL.getCapabilities();

if (caps.OpenGL33) {
    // Use VAOs, instanced rendering, etc.
}
if (caps.GL_ARB_texture_compression) {
    // Use compressed texture uploads
}
The GLXGears sample checks caps.OpenGL30 before calling glGenVertexArrays and falls back to per-bind attribute setup on older drivers.

Uniform variables

Pass per-draw data to shaders via uniform locations:
int uMVP   = glGetUniformLocation(program, "u_MVP");
int uColor = glGetUniformLocation(program, "u_COLOR");

// Each frame, before drawing:
glUniformMatrix4fv(uMVP,   false, matrixFloatBuffer);
glUniform4fv(uColor, colorFloatBuffer);

Cleaning up

Free GPU resources in reverse order of creation. The Gears demo frees the debugProc callback and the OpenGL capabilities buffer on shutdown:
if (debugProc != null) {
    debugProc.free();
}
GL.setCapabilities(null);  // clears the thread-local capability reference
Shader and buffer objects should be deleted with glDeleteShader, glDeleteProgram, glDeleteBuffers, and glDeleteVertexArrays when they are no longer needed.

Build docs developers (and LLMs) love