libgdx/libgdx · error · ReadOnlyBufferException

ReadOnlyBufferException

Error message

ReadOnlyBufferException

What it means

Thrown by FloatToByteBufferAdapter.compact() when the underlying ByteBuffer is read-only. This adapter wraps a ByteBuffer to present it as a FloatBuffer; compacting moves data within the backing store, which is illegal on a buffer obtained via asReadOnlyBuffer(). The check mirrors the JDK contract that compact() throws ReadOnlyBufferException on read-only buffers.

Source

Thrown at backends/gdx-backends-gwt/src/com/badlogic/gdx/backends/gwt/emu/java/nio/FloatToByteBufferAdapter.java:108

// ((DirectBuffer) byteBuffer).free();
// } else {
// assert false : byteBuffer;
// }
// }

	@Override
	public FloatBuffer asReadOnlyBuffer () {
		FloatToByteBufferAdapter buf = new FloatToByteBufferAdapter(byteBuffer.asReadOnlyBuffer());
		buf.limit = limit;
		buf.position = position;
		buf.mark = mark;
		return buf;
	}

	@Override
	public FloatBuffer compact () {
		if (byteBuffer.isReadOnly()) {
			throw new ReadOnlyBufferException();
		}
		byteBuffer.limit(limit << 2);
		byteBuffer.position(position << 2);
		byteBuffer.compact();
		byteBuffer.clear();
		position = limit - position;
		limit = capacity;
		mark = UNSET_MARK;
		return this;
	}

	@Override
	public FloatBuffer duplicate () {
		FloatToByteBufferAdapter buf = new FloatToByteBufferAdapter(byteBuffer.duplicate());
		buf.limit = limit;
		buf.position = position;
		buf.mark = mark;
		return buf;

View on GitHub (pinned to 97f4086187)

Solutions

  1. Compact the original writable FloatBuffer, not the read-only view: keep a reference to the writable buffer and only expose asReadOnlyBuffer() copies to consumers.
  2. Check isReadOnly() before compacting: if (!buf.isReadOnly()) buf.compact();
  3. Rebuild the buffer contents into a fresh writable buffer (FloatBuffer.allocate + put) instead of compacting the read-only one.
  4. Wrap the compact() call in try-catch (ReadOnlyBufferException) and fall back to reallocating.

Example fix

// before
FloatBuffer ro = writable.asReadOnlyBuffer();
ro.compact(); // ReadOnlyBufferException

// after
if (!ro.isReadOnly()) {
    ro.compact();
} else {
    writable.compact(); // compact the original writable buffer
}
Defensive patterns

Strategy: validation

Validate before calling

// before buf.compact()
if (buf.isReadOnly()) {
    throw new IllegalStateException("cannot compact a read-only buffer");
}

Try / catch

try { buf.compact(); } catch (ReadOnlyBufferException e) { /* fall back to copy into a new writable buffer */ }

Prevention

When it happens

Trigger: Calling compact() on a FloatBuffer returned by FloatToByteBufferAdapter.asReadOnlyBuffer(), or on a FloatBuffer view whose underlying ByteBuffer was itself created from a read-only byte buffer.

Common situations: Chaining asReadOnlyBuffer() to hand a buffer to untrusted rendering code, then later calling compact() on the same view during buffer reuse in a GWT mesh uploader; sharing a read-only byte buffer between threads and trying to compact after partial consumption.

Related errors


AI-assisted analysis of libgdx/libgdx@97f4086187 (2026-08-14). Data as JSON: /api/errors/2f7f1127d2379151. Report an issue: GitHub.