{"record":{"id":"2f7f1127d2379151","repo":"libgdx/libgdx","slug":"readonlybufferexception-2f7f11","errorCode":null,"errorMessage":"ReadOnlyBufferException","messagePattern":"ReadOnlyBufferException","errorType":"exception","errorClass":"ReadOnlyBufferException","httpStatus":null,"severity":"error","filePath":"backends/gdx-backends-gwt/src/com/badlogic/gdx/backends/gwt/emu/java/nio/FloatToByteBufferAdapter.java","lineNumber":108,"sourceCode":"// ((DirectBuffer) byteBuffer).free();\n// } else {\n// assert false : byteBuffer;\n// }\n// }\n\n\t@Override\n\tpublic FloatBuffer asReadOnlyBuffer () {\n\t\tFloatToByteBufferAdapter buf = new FloatToByteBufferAdapter(byteBuffer.asReadOnlyBuffer());\n\t\tbuf.limit = limit;\n\t\tbuf.position = position;\n\t\tbuf.mark = mark;\n\t\treturn buf;\n\t}\n\n\t@Override\n\tpublic FloatBuffer compact () {\n\t\tif (byteBuffer.isReadOnly()) {\n\t\t\tthrow new ReadOnlyBufferException();\n\t\t}\n\t\tbyteBuffer.limit(limit << 2);\n\t\tbyteBuffer.position(position << 2);\n\t\tbyteBuffer.compact();\n\t\tbyteBuffer.clear();\n\t\tposition = limit - position;\n\t\tlimit = capacity;\n\t\tmark = UNSET_MARK;\n\t\treturn this;\n\t}\n\n\t@Override\n\tpublic FloatBuffer duplicate () {\n\t\tFloatToByteBufferAdapter buf = new FloatToByteBufferAdapter(byteBuffer.duplicate());\n\t\tbuf.limit = limit;\n\t\tbuf.position = position;\n\t\tbuf.mark = mark;\n\t\treturn buf;","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/libgdx/libgdx/blob/97f40861878058087be0685ca167ddfde132134b/backends/gdx-backends-gwt/src/com/badlogic/gdx/backends/gwt/emu/java/nio/FloatToByteBufferAdapter.java#L90-L126","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Compact the original writable FloatBuffer, not the read-only view: keep a reference to the writable buffer and only expose asReadOnlyBuffer() copies to consumers.","Check isReadOnly() before compacting: if (!buf.isReadOnly()) buf.compact();","Rebuild the buffer contents into a fresh writable buffer (FloatBuffer.allocate + put) instead of compacting the read-only one.","Wrap the compact() call in try-catch (ReadOnlyBufferException) and fall back to reallocating."],"exampleFix":"// before\nFloatBuffer ro = writable.asReadOnlyBuffer();\nro.compact(); // ReadOnlyBufferException\n\n// after\nif (!ro.isReadOnly()) {\n    ro.compact();\n} else {\n    writable.compact(); // compact the original writable buffer\n}","handlingStrategy":"validation","validationCode":"// before buf.compact()\nif (buf.isReadOnly()) {\n    throw new IllegalStateException(\"cannot compact a read-only buffer\");\n}","typeGuard":null,"tryCatchPattern":"try { buf.compact(); } catch (ReadOnlyBufferException e) { /* fall back to copy into a new writable buffer */ }","preventionTips":["Keep the writable master buffer as the owner; only hand out asReadOnlyBuffer() copies.","Check isReadOnly() before any state-mutating operation (compact, put).","Treat read-only views as consumed data, never as scratch space for reuse."],"tags":["libgdx","gwt","java-nio","readonly-buffer","compact"],"backgroundTag":null,"analyzedSha":"97f40861878058087be0685ca167ddfde132134b","analyzedAt":"2026-08-14T10:52:53.492Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}