[jnigen] Add `JBuffer`, `JByteBuffer` to enable fast copying to and from `Uint8List` (https://github.com/dart-lang/jnigen/issues/390)

`JBuffer` is now the default binding for `java.nio.Buffer` and `JByteBuffer` for `java.nio.ByteBuffer`.
diff --git a/pkgs/jni/CHANGELOG.md b/pkgs/jni/CHANGELOG.md
index f997531..64ddb4d 100644
--- a/pkgs/jni/CHANGELOG.md
+++ b/pkgs/jni/CHANGELOG.md
@@ -1,33 +1,57 @@
+## 0.7.0
+
+- **Breaking Change** ([#387](https://github.com/dart-lang/jnigen/issues/387)):
+  Added `JBuffer` and `JByteBuffer` classes as default classes for
+  `java.nio.Buffer` and `java.nio.ByteBuffer` respectively.
+- **Breaking Change**: Made the type classes `final`.
+- Fixed a bug where `addAll`, `removeAll` and `retainAll` in `JSet` would run
+  their respective operation twice.
+- Fixed a bug where `JList.insertAll` would not throw the potentially thrown
+  Java exception.
+
 ## 0.6.1
-* Depend on the stable version of Dart 3.1.
+
+- Depend on the stable version of Dart 3.1.
 
 ## 0.6.0
-* **Breaking Change** ([#131](https://github.com/dart-lang/jnigen/issues/131)): Renamed `delete*` to `release*`.
-* Added `PortProxy` and related methods used for interface implementation.
-* Added the missing binding for `java.lang.Character`.
+
+- **Breaking Change** ([#131](https://github.com/dart-lang/jnigen/issues/131)):
+  Renamed `delete*` to `release*`.
+- Added `PortProxy` and related methods used for interface implementation.
+- Added the missing binding for `java.lang.Character`.
 
 ## 0.5.0
-* **Breaking Change** ([#137](https://github.com/dart-lang/jnigen/issues/137)): Java primitive types are now all lowercase like `jint`, `jshort`, ...
-* The bindings for `java.util.Set`, `java.util.Map`, `java.util.List` and the numeric types like `java.lang.Integer`, `java.lang.Boolean`, ... are now included in `package:jni`.
+
+- **Breaking Change** ([#137](https://github.com/dart-lang/jnigen/issues/137)):
+  Java primitive types are now all lowercase like `jint`, `jshort`, ...
+- The bindings for `java.util.Set`, `java.util.Map`, `java.util.List` and the
+  numeric types like `java.lang.Integer`, `java.lang.Boolean`, ... are now
+  included in `package:jni`.
 
 ## 0.4.0
-* Type classes now have `superCount` and `superType` getters used for type inference.
+
+- Type classes now have `superCount` and `superType` getters used for type
+  inference.
 
 ## 0.3.0
-* Added `PortContinuation` used for `suspend fun` in Kotlin.
-* `dartjni` now depends on `dart_api_dl.h`.
+
+- Added `PortContinuation` used for `suspend fun` in Kotlin.
+- `dartjni` now depends on `dart_api_dl.h`.
 
 ## 0.2.1
-* Added `.clang-format` to pub.
+
+- Added `.clang-format` to pub.
 
 ## 0.2.0
-* Added array support
-* Added generic support
-* `JniX` turned into `JX` for a more terse code.
+
+- Added array support
+- Added generic support
+- `JniX` turned into `JX` for a more terse code.
 
 ## 0.1.1
-* Windows support for running tests and examples on development machines.
+
+- Windows support for running tests and examples on development machines.
 
 ## 0.1.0
-* Initial version: Android and Linux support, JObject API
 
+- Initial version: Android and Linux support, JObject API
diff --git a/pkgs/jni/lib/jni.dart b/pkgs/jni/lib/jni.dart
index d63fc73..85f99fa 100644
--- a/pkgs/jni/lib/jni.dart
+++ b/pkgs/jni/lib/jni.dart
@@ -71,6 +71,7 @@
 export 'src/jprimitives.dart';
 export 'src/jreference.dart' show JReferenceUseExtension;
 export 'src/lang/lang.dart';
+export 'src/nio/nio.dart';
 export 'src/util/util.dart';
 
 export 'package:ffi/ffi.dart' show using, Arena;
diff --git a/pkgs/jni/lib/jni_symbols.yaml b/pkgs/jni/lib/jni_symbols.yaml
index 5159dc9..ac4a74c 100644
--- a/pkgs/jni/lib/jni_symbols.yaml
+++ b/pkgs/jni/lib/jni_symbols.yaml
@@ -75,3 +75,11 @@
           'java.lang.Object': DECLARED
         V:
           'java.lang.Object': DECLARED
+    'java.nio.Buffer':
+      name: JBuffer
+      type_class: JBufferType
+      super_count: 1
+    'java.nio.ByteBuffer':
+      name: JByteBuffer
+      type_class: JByteBufferType
+      super_count: 2
diff --git a/pkgs/jni/lib/src/jarray.dart b/pkgs/jni/lib/src/jarray.dart
index 1b97039..fac1d85 100644
--- a/pkgs/jni/lib/src/jarray.dart
+++ b/pkgs/jni/lib/src/jarray.dart
@@ -16,7 +16,7 @@
 import 'jprimitives.dart';
 import 'types.dart';
 
-class JArrayType<T> extends JObjType<JArray<T>> {
+final class JArrayType<T> extends JObjType<JArray<T>> {
   final JType<T> elementType;
 
   const JArrayType(this.elementType);
diff --git a/pkgs/jni/lib/src/jobject.dart b/pkgs/jni/lib/src/jobject.dart
index 6a2cad2..a7e659c 100644
--- a/pkgs/jni/lib/src/jobject.dart
+++ b/pkgs/jni/lib/src/jobject.dart
@@ -19,7 +19,7 @@
 // type switch like a regular type.
 typedef _VoidType = void;
 
-class JObjectType extends JObjType<JObject> {
+final class JObjectType extends JObjType<JObject> {
   const JObjectType();
 
   @override
diff --git a/pkgs/jni/lib/src/lang/jboolean.dart b/pkgs/jni/lib/src/lang/jboolean.dart
index 3dd22b1..9ea92b4 100644
--- a/pkgs/jni/lib/src/lang/jboolean.dart
+++ b/pkgs/jni/lib/src/lang/jboolean.dart
@@ -9,7 +9,7 @@
 import '../third_party/generated_bindings.dart';
 import '../types.dart';
 
-class JBooleanType extends JObjType<JBoolean> {
+final class JBooleanType extends JObjType<JBoolean> {
   const JBooleanType();
 
   @override
diff --git a/pkgs/jni/lib/src/lang/jbyte.dart b/pkgs/jni/lib/src/lang/jbyte.dart
index 3cb9e96..6b5aa98 100644
--- a/pkgs/jni/lib/src/lang/jbyte.dart
+++ b/pkgs/jni/lib/src/lang/jbyte.dart
@@ -9,7 +9,7 @@
 import '../types.dart';
 import 'jnumber.dart';
 
-class JByteType extends JObjType<JByte> {
+final class JByteType extends JObjType<JByte> {
   const JByteType();
 
   @override
diff --git a/pkgs/jni/lib/src/lang/jcharacter.dart b/pkgs/jni/lib/src/lang/jcharacter.dart
index c43d2e0..b50f67f 100644
--- a/pkgs/jni/lib/src/lang/jcharacter.dart
+++ b/pkgs/jni/lib/src/lang/jcharacter.dart
@@ -6,7 +6,7 @@
 import '../third_party/generated_bindings.dart';
 import '../types.dart';
 
-class JCharacterType extends JObjType<JCharacter> {
+final class JCharacterType extends JObjType<JCharacter> {
   const JCharacterType();
 
   @override
diff --git a/pkgs/jni/lib/src/lang/jdouble.dart b/pkgs/jni/lib/src/lang/jdouble.dart
index c6852a1..6db163e 100644
--- a/pkgs/jni/lib/src/lang/jdouble.dart
+++ b/pkgs/jni/lib/src/lang/jdouble.dart
@@ -8,7 +8,7 @@
 import '../types.dart';
 import 'jnumber.dart';
 
-class JDoubleType extends JObjType<JDouble> {
+final class JDoubleType extends JObjType<JDouble> {
   const JDoubleType();
 
   @override
diff --git a/pkgs/jni/lib/src/lang/jfloat.dart b/pkgs/jni/lib/src/lang/jfloat.dart
index f23c237..a352659 100644
--- a/pkgs/jni/lib/src/lang/jfloat.dart
+++ b/pkgs/jni/lib/src/lang/jfloat.dart
@@ -9,7 +9,7 @@
 import '../types.dart';
 import 'jnumber.dart';
 
-class JFloatType extends JObjType<JFloat> {
+final class JFloatType extends JObjType<JFloat> {
   const JFloatType();
 
   @override
diff --git a/pkgs/jni/lib/src/lang/jinteger.dart b/pkgs/jni/lib/src/lang/jinteger.dart
index 907d7ba..8ece466 100644
--- a/pkgs/jni/lib/src/lang/jinteger.dart
+++ b/pkgs/jni/lib/src/lang/jinteger.dart
@@ -9,7 +9,7 @@
 import '../types.dart';
 import 'jnumber.dart';
 
-class JIntegerType extends JObjType<JInteger> {
+final class JIntegerType extends JObjType<JInteger> {
   const JIntegerType();
 
   @override
diff --git a/pkgs/jni/lib/src/lang/jlong.dart b/pkgs/jni/lib/src/lang/jlong.dart
index fb2ee80..66d054b 100644
--- a/pkgs/jni/lib/src/lang/jlong.dart
+++ b/pkgs/jni/lib/src/lang/jlong.dart
@@ -8,7 +8,7 @@
 import '../types.dart';
 import 'jnumber.dart';
 
-class JLongType extends JObjType<JLong> {
+final class JLongType extends JObjType<JLong> {
   const JLongType();
 
   @override
diff --git a/pkgs/jni/lib/src/lang/jnumber.dart b/pkgs/jni/lib/src/lang/jnumber.dart
index 43e1085..45d86dc 100644
--- a/pkgs/jni/lib/src/lang/jnumber.dart
+++ b/pkgs/jni/lib/src/lang/jnumber.dart
@@ -17,7 +17,7 @@
 import 'jlong.dart';
 import 'jshort.dart';
 
-class JNumberType extends JObjType<JNumber> {
+final class JNumberType extends JObjType<JNumber> {
   const JNumberType();
 
   @override
diff --git a/pkgs/jni/lib/src/lang/jshort.dart b/pkgs/jni/lib/src/lang/jshort.dart
index a07ea4e..39d3c1d 100644
--- a/pkgs/jni/lib/src/lang/jshort.dart
+++ b/pkgs/jni/lib/src/lang/jshort.dart
@@ -9,7 +9,7 @@
 import '../types.dart';
 import 'jnumber.dart';
 
-class JShortType extends JObjType<JShort> {
+final class JShortType extends JObjType<JShort> {
   const JShortType();
 
   @override
diff --git a/pkgs/jni/lib/src/lang/jstring.dart b/pkgs/jni/lib/src/lang/jstring.dart
index 43bda60..9e542ff 100644
--- a/pkgs/jni/lib/src/lang/jstring.dart
+++ b/pkgs/jni/lib/src/lang/jstring.dart
@@ -12,7 +12,7 @@
 import '../third_party/generated_bindings.dart';
 import '../types.dart';
 
-class JStringType extends JObjType<JString> {
+final class JStringType extends JObjType<JString> {
   const JStringType();
 
   @override
diff --git a/pkgs/jni/lib/src/nio/jbuffer.dart b/pkgs/jni/lib/src/nio/jbuffer.dart
new file mode 100644
index 0000000..cae6bc9
--- /dev/null
+++ b/pkgs/jni/lib/src/nio/jbuffer.dart
@@ -0,0 +1,255 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../accessors.dart';
+import '../jni.dart';
+import '../jobject.dart';
+import '../jvalues.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+
+final class JBufferType extends JObjType<JBuffer> {
+  const JBufferType();
+
+  @override
+  String get signature => r"Ljava/nio/Buffer;";
+
+  @override
+  JBuffer fromRef(JObjectPtr ref) => JBuffer.fromRef(ref);
+
+  @override
+  JObjType get superType => const JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => (JBufferType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == (JBufferType) && other is JBufferType;
+  }
+}
+
+/// A container for data of a specific primitive type.
+///
+/// The bindings for `java.nio.Buffer`.
+///
+/// A buffer is a linear, finite sequence of elements of a specific primitive
+/// type. Aside from its content, the essential properties of a buffer are its
+/// [capacity], [limit], and [position].
+///
+/// There is one subclass of this class for each non-boolean primitive type.
+/// We currently only have the bindings for `java.nio.ByteBuffer` in this
+/// package as [JByteBuffer].
+class JBuffer extends JObject {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JBuffer> $type = type;
+
+  JBuffer.fromRef(
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = Jni.findJClass(r"java/nio/Buffer");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = JBufferType();
+
+  static final _capacityId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"capacity", r"()I");
+
+  /// The number of elements this buffer contains.
+  ///
+  /// It is never negative and never changes.
+  int get capacity {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _capacityId, JniCallType.intType, []).integer;
+  }
+
+  static final _positionId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"position", r"()I");
+
+  /// The index of the next element to be read or written.
+  ///
+  /// It is never negative and is never greater than its [limit].
+  int get position {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _positionId, JniCallType.intType, []).integer;
+  }
+
+  static final _setPositionId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"position", r"(I)Ljava/nio/Buffer;");
+
+  /// Throws:
+  /// * [IllegalArgumentException] - If the preconditions on [newPosition] do
+  ///   not hold.
+  set position(int position) {
+    Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(reference,
+        _setPositionId, JniCallType.objectType, [JValueInt(position)]).object);
+  }
+
+  static final _limitId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"limit", r"()I");
+
+  /// The index of the first element that should not be read or written.
+  ///
+  /// It is never negative and is never greater than its [capacity].
+  int get limit {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _limitId, JniCallType.intType, []).integer;
+  }
+
+  static final _setLimitId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"limit", r"(I)Ljava/nio/Buffer;");
+
+  /// Throws:
+  /// * [IllegalArgumentException] - If the preconditions on [newLimit] do not
+  ///   hold.
+  set limit(int newLimit) {
+    Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(reference,
+        _setLimitId, JniCallType.objectType, [JValueInt(newLimit)]).object);
+  }
+
+  static final _markId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"mark", r"()Ljava/nio/Buffer;");
+
+  /// Sets this buffer's mark at its [position].
+  ///
+  /// Mark is the index to which its [position] will be reset when the [reset]
+  /// method is invoked.
+  void mark() {
+    Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(
+        reference, _markId, JniCallType.objectType, []).object);
+  }
+
+  static final _resetId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"reset", r"()Ljava/nio/Buffer;");
+
+  /// Resets this buffer's [position] to the previously-marked position.
+  ///
+  /// Throws:
+  /// * [InvalidMarkException] - If the mark has not been set
+  void reset() {
+    Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(
+        reference, _resetId, JniCallType.objectType, []).object);
+  }
+
+  static final _clearId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"clear", r"()Ljava/nio/Buffer;");
+
+  /// Clears this buffer.
+  ///
+  /// The [position] is set to zero, the [limit] is set to
+  /// the [capacity], and the mark is discarded.
+  void clear() {
+    Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(
+        reference, _clearId, JniCallType.objectType, []).object);
+  }
+
+  static final _flipId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"flip", r"()Ljava/nio/Buffer;");
+
+  /// Flips this buffer.
+  ///
+  /// The limit is set to the current [position] and then the [position] is set
+  /// to zero. If the mark is defined then it is discarded.
+  void flip() {
+    Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(
+        reference, _flipId, JniCallType.objectType, []).object);
+  }
+
+  static final _rewindId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"rewind", r"()Ljava/nio/Buffer;");
+
+  /// Rewinds this buffer.
+  ///
+  /// The [position] is set to zero and the mark is discarded.
+  void rewind() {
+    Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(
+        reference, _rewindId, JniCallType.objectType, []).object);
+  }
+
+  static final _remainingId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"remaining", r"()I");
+
+  /// The number of elements between the current [position] and the
+  /// [limit].
+  int get remaining {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _remainingId, JniCallType.intType, []).integer;
+  }
+
+  static final _hasRemainingId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"hasRemaining", r"()Z");
+
+  /// Whether there are any elements between the current [position] and
+  /// the [limit].
+  bool get hasRemaining {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _hasRemainingId, JniCallType.booleanType, []).boolean;
+  }
+
+  static final _isReadOnlyId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"isReadOnly", r"()Z");
+
+  /// Whether or not this buffer is read-only.
+  bool get isReadOnly {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _isReadOnlyId, JniCallType.booleanType, []).boolean;
+  }
+
+  static final _hasArrayId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"hasArray", r"()Z");
+
+  /// Whether or not this buffer is backed by an accessible array.
+  bool get hasArray {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _hasArrayId, JniCallType.booleanType, []).boolean;
+  }
+
+  static final _arrayId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"array", r"()Ljava/lang/Object;");
+
+  /// The array that backs this buffer.
+  ///
+  /// Concrete subclasses like [JByteBuffer] provide more strongly-typed return
+  /// values for this method.
+  ///
+  /// Throws:
+  /// * [ReadOnlyBufferException] - If this buffer is backed by an array but is
+  ///   read-only
+  /// * [UnsupportedOperationException] - If this buffer is not backed by an
+  ///   accessible array
+  JObject get array {
+    return const JObjectType().fromRef(Jni.accessors.callMethodWithArgs(
+        reference, _arrayId, JniCallType.objectType, []).object);
+  }
+
+  static final _arrayOffsetId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"arrayOffset", r"()I");
+
+  /// The offset within this buffer's backing array of the first element
+  /// of the buffer.
+  ///
+  /// Throws:
+  /// * [ReadOnlyBufferException] - If this buffer is backed by an array but is
+  ///   read-only
+  /// * [UnsupportedOperationException] - If this buffer is not backed by an
+  ///   accessible array
+  int get arrayOffset {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _arrayOffsetId, JniCallType.intType, []).integer;
+  }
+
+  static final _isDirectId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"isDirect", r"()Z");
+
+  /// Whether or not this buffer is direct.
+  bool get isDirect {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _isDirectId, JniCallType.booleanType, []).boolean;
+  }
+}
diff --git a/pkgs/jni/lib/src/nio/jbyte_buffer.dart b/pkgs/jni/lib/src/nio/jbyte_buffer.dart
new file mode 100644
index 0000000..1b0acb2
--- /dev/null
+++ b/pkgs/jni/lib/src/nio/jbyte_buffer.dart
@@ -0,0 +1,316 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:ffi';
+import 'dart:typed_data';
+
+import '../accessors.dart';
+import '../jarray.dart';
+import '../jni.dart';
+import '../jprimitives.dart';
+import '../jreference.dart';
+import '../jvalues.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+import 'jbuffer.dart';
+
+final class JByteBufferType extends JObjType<JByteBuffer> {
+  const JByteBufferType();
+
+  @override
+  String get signature => r"Ljava/nio/ByteBuffer;";
+
+  @override
+  JByteBuffer fromRef(JObjectPtr ref) => JByteBuffer.fromRef(ref);
+
+  @override
+  JObjType get superType => const JBufferType();
+
+  @override
+  final superCount = 2;
+
+  @override
+  int get hashCode => (JByteBufferType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == (JByteBufferType) && other is JByteBufferType;
+  }
+}
+
+/// A byte [JBuffer].
+///
+/// The bindings for `java.nio.ByteBuffer`.
+///
+/// This enables fast memory copying between Java and Dart when directly
+/// allocated (See [JByteBuffer.allocateDirect]).
+///
+/// To create a [JByteBuffer] from the content of a [Uint8List],
+/// use [JByteBuffer.fromList]. This uses direct allocation to enable fast
+/// copying.
+///
+/// [asUint8List] provides a direct access to the underlying [Uint8List] that
+/// this buffer uses. This means any changes to it will change the content of
+/// the buffer and vice versa. This can be used to access to [Uint8List] methods
+/// such as [Uint8List.setRange].
+///
+/// Example:
+/// ```dart
+/// final directBuffer = JByteBuffer.allocateDirect(3);
+/// directBuffer.asUint8List().setAll(0, [1, 2, 3]);
+/// // The buffer is now 1, 2, 3.
+/// ```
+///
+/// Both the original buffer and the [Uint8List] keep the underlying Java buffer
+/// alive. Once all the instances of the original buffer and the lists produced
+/// from [asUint8List] are inaccessible both in Java and Dart, Java will
+/// correctly garbage collects the buffer and frees its underlying memory.
+///
+/// Example:
+/// ```dart
+/// final directBuffer = JByteBuffer.allocateDirect(3);
+/// final data = directBuffer.asUint8List();
+/// directBuffer.release(); // Releasing the original buffer.
+/// data.setAll(0, [1, 2, 3]); // Works! [data] is still accessible.
+/// ```
+///
+/// The original buffer can be [release]d when calling [asUint8List]
+/// by setting the `releaseOriginal` parameter to `true`.
+///
+/// Example:
+/// ```dart
+/// final directBuffer = JByteBuffer.allocateDirect(3);
+/// // [releaseOriginal] is `false` by default.
+/// final data1 = directBuffer.asUint8List();
+/// directBuffer.nextByte = 42; // No problem!
+/// print(data1[0]); // prints 42!
+/// final data2 = directBuffer.asUint8List(releaseOriginal: true);
+/// // directBuffer.nextByte = 42; // throws [UseAfterReleaseException]!
+/// ```
+class JByteBuffer extends JBuffer {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JByteBuffer> $type = type;
+
+  JByteBuffer.fromRef(
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = Jni.findJClass(r"java/nio/ByteBuffer");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = JByteBufferType();
+
+  static final _allocateDirectId = Jni.accessors.getStaticMethodIDOf(
+      _class.reference, r"allocateDirect", r"(I)Ljava/nio/ByteBuffer;");
+
+  /// Allocates a new direct byte buffer.
+  ///
+  /// Throws:
+  /// * [IllegalArgumentException] - If the capacity is a negative integer
+  factory JByteBuffer.allocateDirect(int capacity) {
+    return JByteBuffer.fromRef(
+      Jni.accessors.callStaticMethodWithArgs(
+          _class.reference,
+          _allocateDirectId,
+          JniCallType.objectType,
+          [JValueInt(capacity)]).object,
+    );
+  }
+
+  static final _allocateId = Jni.accessors.getStaticMethodIDOf(
+      _class.reference, r"allocate", r"(I)Ljava/nio/ByteBuffer;");
+
+  /// Allocates a new byte buffer.
+  ///
+  /// Throws:
+  /// * [IllegalArgumentException] - If the capacity is a negative integer
+  factory JByteBuffer.allocate(int capacity) {
+    return const JByteBufferType().fromRef(Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _allocateId,
+            JniCallType.objectType, [JValueInt(capacity)]).object);
+  }
+
+  static final _wrapWholeId = Jni.accessors.getStaticMethodIDOf(
+      _class.reference, r"wrap", r"([B)Ljava/nio/ByteBuffer;");
+  static final _wrapId = Jni.accessors.getStaticMethodIDOf(
+      _class.reference, r"wrap", r"([BII)Ljava/nio/ByteBuffer;");
+
+  /// Wraps a byte array into a buffer.
+  ///
+  /// The new buffer will be backed by the given byte array; that is,
+  /// modifications to the buffer will cause the array to be modified
+  /// and vice versa.
+  static JByteBuffer wrap(
+    JArray<jbyte> array, [
+    int? offset,
+    int? length,
+  ]) {
+    if (offset == null && length == null) {
+      return const JByteBufferType().fromRef(
+        Jni.accessors.callStaticMethodWithArgs(
+          _class.reference,
+          _wrapWholeId,
+          JniCallType.objectType,
+          [array.reference],
+        ).object,
+      );
+    }
+    offset ??= 0;
+    length ??= array.length - offset;
+    return const JByteBufferType().fromRef(
+      Jni.accessors.callStaticMethodWithArgs(
+        _class.reference,
+        _wrapId,
+        JniCallType.objectType,
+        [array.reference, JValueInt(offset), JValueInt(length)],
+      ).object,
+    );
+  }
+
+  /// Creates a [JByteBuffer] from the content of [list].
+  ///
+  /// The [JByteBuffer] will be allocated using [JByteBuffer.allocateDirect].
+  factory JByteBuffer.fromList(Uint8List list) {
+    final buffer = JByteBuffer.allocateDirect(list.length);
+    buffer._asUint8ListUnsafe().setAll(0, list);
+    return buffer;
+  }
+
+  static final _sliceId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"slice", r"()Ljava/nio/ByteBuffer;");
+
+  /// Creates a new byte buffer whose content is a shared subsequence of this
+  /// buffer's content.
+  JByteBuffer slice() {
+    return const JByteBufferType().fromRef(Jni.accessors.callMethodWithArgs(
+        reference, _sliceId, JniCallType.objectType, []).object);
+  }
+
+  static final _duplicateId = Jni.accessors.getMethodIDOf(
+      _class.reference, r"duplicate", r"()Ljava/nio/ByteBuffer;");
+
+  /// Creates a new byte buffer that shares this buffer's content.
+  JByteBuffer duplicate() {
+    return const JByteBufferType().fromRef(Jni.accessors.callMethodWithArgs(
+        reference, _duplicateId, JniCallType.objectType, []).object);
+  }
+
+  static final _asReadOnlyBufferId = Jni.accessors.getMethodIDOf(
+      _class.reference, r"asReadOnlyBuffer", r"()Ljava/nio/ByteBuffer;");
+
+  /// Creates a new, read-only byte buffer that shares this buffer's content.
+  JByteBuffer asReadOnlyBuffer() {
+    return const JByteBufferType().fromRef(Jni.accessors.callMethodWithArgs(
+        reference, _asReadOnlyBufferId, JniCallType.objectType, []).object);
+  }
+
+  static final _getId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"get", r"()B");
+
+  /// Reads the byte at this buffer's current [position], and then increments the
+  /// [position].
+  ///
+  /// Throws:
+  ///  * [BufferOverflowException] - If the buffer's current [position] is not
+  ///    smaller than its [limit]
+  int get nextByte {
+    return Jni.accessors
+        .callMethodWithArgs(reference, _getId, JniCallType.byteType, []).byte;
+  }
+
+  static final _putId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"put", r"(B)Ljava/nio/ByteBuffer;");
+
+  /// Writes the given byte into this buffer at the current [position], and then
+  /// increments the [position].
+  ///
+  /// Throws:
+  /// * [BufferOverflowException] - If this buffer's current [position] is not
+  ///   smaller than its [limit]
+  /// * [ReadOnlyBufferException] - If this buffer is read-only
+  set nextByte(int b) {
+    Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(
+        reference, _putId, JniCallType.objectType, [JValueByte(b)]).object);
+  }
+
+  static final _arrayId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"array", r"()[B");
+
+  @override
+  JArray<jbyte> get array {
+    return const JArrayType(jbyteType()).fromRef(Jni.accessors
+        .callMethodWithArgs(
+            reference, _arrayId, JniCallType.objectType, []).object);
+  }
+
+  void _ensureIsDirect() {
+    if (!isDirect) {
+      throw StateError(
+        'The buffer must be created with `JByteBuffer.allocateDirect`.',
+      );
+    }
+  }
+
+  Pointer<Void> _directBufferAddress() {
+    final address = Jni.env.GetDirectBufferAddress(reference);
+    if (address == nullptr) {
+      throw StateError(
+        'The memory region is undefined or '
+        'direct buffer access is not supported by this JVM.',
+      );
+    }
+    return address;
+  }
+
+  int _directBufferCapacity() {
+    final capacity = Jni.env.GetDirectBufferCapacity(reference);
+    if (capacity == -1) {
+      throw StateError(
+        'The object is an unaligned view buffer and the processor '
+        'architecture does not support unaligned access.',
+      );
+    }
+    return capacity;
+  }
+
+  Uint8List _asUint8ListUnsafe() {
+    _ensureIsDirect();
+    final address = _directBufferAddress();
+    final capacity = _directBufferCapacity();
+    return address.cast<Uint8>().asTypedList(capacity);
+  }
+
+  /// Returns this byte buffer as a [Uint8List].
+  ///
+  /// If [releaseOriginal] is `true`, this byte buffer will be released.
+  ///
+  /// Throws [StateError] if the buffer is not direct
+  /// (see [JByteBuffer.allocateDirect]) or the JVM does not support the direct
+  /// buffer operations or the object is an unaligned view buffer and
+  /// the processor does not support unaligned access.
+  Uint8List asUint8List({bool releaseOriginal = false}) {
+    _ensureIsDirect();
+    final address = _directBufferAddress();
+    final capacity = _directBufferCapacity();
+    final token = releaseOriginal ? reference : Jni.env.NewGlobalRef(reference);
+    if (releaseOriginal) {
+      setAsReleased();
+    }
+    return address.cast<Uint8>().asTypedList(
+          capacity,
+          token: token,
+          finalizer: Jni.env.ptr.ref.DeleteGlobalRef.cast(),
+        );
+  }
+}
+
+extension Uint8ListToJava on Uint8List {
+  /// Creates a [JByteBuffer] from the content of this list.
+  ///
+  /// The [JByteBuffer] will be allocated using [JByteBuffer.allocateDirect].
+  JByteBuffer toJByteBuffer() {
+    return JByteBuffer.fromList(this);
+  }
+}
diff --git a/pkgs/jni/lib/src/nio/nio.dart b/pkgs/jni/lib/src/nio/nio.dart
new file mode 100644
index 0000000..3092870
--- /dev/null
+++ b/pkgs/jni/lib/src/nio/nio.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+export 'jbuffer.dart';
+export 'jbyte_buffer.dart';
diff --git a/pkgs/jni/lib/src/util/jiterator.dart b/pkgs/jni/lib/src/util/jiterator.dart
index eab305d..0d0122f 100644
--- a/pkgs/jni/lib/src/util/jiterator.dart
+++ b/pkgs/jni/lib/src/util/jiterator.dart
@@ -8,7 +8,7 @@
 import '../third_party/generated_bindings.dart';
 import '../types.dart';
 
-class JIteratorType<$E extends JObject> extends JObjType<JIterator<$E>> {
+final class JIteratorType<$E extends JObject> extends JObjType<JIterator<$E>> {
   final JObjType<$E> E;
 
   const JIteratorType(
diff --git a/pkgs/jni/lib/src/util/jlist.dart b/pkgs/jni/lib/src/util/jlist.dart
index 19cb1ed..9779032 100644
--- a/pkgs/jni/lib/src/util/jlist.dart
+++ b/pkgs/jni/lib/src/util/jlist.dart
@@ -15,7 +15,7 @@
 import '../types.dart';
 import 'jiterator.dart';
 
-class JListType<$E extends JObject> extends JObjType<JList<$E>> {
+final class JListType<$E extends JObject> extends JObjType<JList<$E>> {
   final JObjType<$E> E;
 
   const JListType(
@@ -193,10 +193,11 @@
         Jni.env.IsInstanceOf(
             (iterable as JObject).reference, _collectionClass.reference)) {
       Jni.accessors.callMethodWithArgs(
-          reference,
-          _insertAllId,
-          JniCallType.booleanType,
-          [JValueInt(index), (iterable as JObject).reference]);
+        reference,
+        _insertAllId,
+        JniCallType.booleanType,
+        [JValueInt(index), (iterable as JObject).reference],
+      ).boolean;
       return;
     }
     super.insertAll(index, iterable);
diff --git a/pkgs/jni/lib/src/util/jmap.dart b/pkgs/jni/lib/src/util/jmap.dart
index 015fea8..73a6d62 100644
--- a/pkgs/jni/lib/src/util/jmap.dart
+++ b/pkgs/jni/lib/src/util/jmap.dart
@@ -11,7 +11,7 @@
 import '../types.dart';
 import 'jset.dart';
 
-class JMapType<$K extends JObject, $V extends JObject>
+final class JMapType<$K extends JObject, $V extends JObject>
     extends JObjType<JMap<$K, $V>> {
   final JObjType<$K> K;
   final JObjType<$V> V;
@@ -107,11 +107,9 @@
     if (other is JMap<$K, $V>) {
       Jni.accessors.callMethodWithArgs(reference, _addAllId,
           JniCallType.voidType, [other.reference]).check();
-    } else {
-      for (final entry in other.entries) {
-        this[entry.key] = entry.value;
-      }
+      return;
     }
+    super.addAll(other);
   }
 
   static final _clearId =
diff --git a/pkgs/jni/lib/src/util/jset.dart b/pkgs/jni/lib/src/util/jset.dart
index 367c121..df51003 100644
--- a/pkgs/jni/lib/src/util/jset.dart
+++ b/pkgs/jni/lib/src/util/jset.dart
@@ -12,7 +12,7 @@
 import '../types.dart';
 import 'jiterator.dart';
 
-class JSetType<$E extends JObject> extends JObjType<JSet<$E>> {
+final class JSetType<$E extends JObject> extends JObjType<JSet<$E>> {
   final JObjType<$E> E;
 
   const JSetType(
@@ -93,6 +93,7 @@
         JniCallType.booleanType,
         [(elements as JObject).reference],
       ).boolean;
+      return;
     }
     return super.addAll(elements);
   }
@@ -173,6 +174,7 @@
             (elements as JObject).reference, _collectionClass.reference)) {
       Jni.accessors.callMethodWithArgs(reference, _removeAllId,
           JniCallType.booleanType, [(elements as JObject).reference]).boolean;
+      return;
     }
     return super.removeAll(elements);
   }
@@ -186,6 +188,7 @@
             (elements as JObject).reference, _collectionClass.reference)) {
       Jni.accessors.callMethodWithArgs(reference, _retainAllId,
           JniCallType.booleanType, [(elements as JObject).reference]).boolean;
+      return;
     }
     return super.retainAll(elements);
   }
diff --git a/pkgs/jni/pubspec.yaml b/pkgs/jni/pubspec.yaml
index 62ba94d..46bedce 100644
--- a/pkgs/jni/pubspec.yaml
+++ b/pkgs/jni/pubspec.yaml
@@ -4,7 +4,7 @@
 
 name: jni
 description: A library to access JNI from Dart and Flutter that acts as a support library for package:jnigen.
-version: 0.6.1
+version: 0.7.0
 repository: https://github.com/dart-lang/jnigen/tree/main/jni
 
 topics:
diff --git a/pkgs/jni/test/jarray_test.dart b/pkgs/jni/test/jarray_test.dart
index 3cc7c98..9a9f59c 100644
--- a/pkgs/jni/test/jarray_test.dart
+++ b/pkgs/jni/test/jarray_test.dart
@@ -319,6 +319,12 @@
       expect((arr[0]..releasedBy(arena)).isNull, true);
     });
   });
+  testRunner('JArray of JCharacter', () {
+    using((arena) {
+      final arr = JArray(JCharacter.type, 1)..releasedBy(arena);
+      expect((arr[0]..releasedBy(arena)).isNull, true);
+    });
+  });
   testRunner('JArray of JLong', () {
     using((arena) {
       final arr = JArray(JLong.type, 1)..releasedBy(arena);
diff --git a/pkgs/jni/test/jbyte_buffer_test.dart b/pkgs/jni/test/jbyte_buffer_test.dart
new file mode 100644
index 0000000..f68c47b
--- /dev/null
+++ b/pkgs/jni/test/jbyte_buffer_test.dart
@@ -0,0 +1,229 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:jni/jni.dart';
+import 'package:test/test.dart';
+
+import 'test_util/test_util.dart';
+
+void main() {
+  // Don't forget to initialize JNI.
+  if (!Platform.isAndroid) {
+    checkDylibIsUpToDate();
+    Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
+  }
+  run(testRunner: test);
+}
+
+void run({required TestRunnerCallback testRunner}) {
+  final throwsAJniException = throwsA(isA<JniException>());
+  JByteBuffer testDataBuffer(Arena arena) {
+    final buffer = JByteBuffer.allocate(3)..releasedBy(arena);
+    buffer.nextByte = 1;
+    buffer.nextByte = 2;
+    buffer.nextByte = 3;
+    buffer.position = 0;
+    return buffer;
+  }
+
+  testRunner('wrap whole array', () {
+    using((arena) {
+      final array = JArray(jbyte.type, 3)..releasedBy(arena);
+      array[0] = 1;
+      array[1] = 2;
+      array[2] = 3;
+      final buffer = JByteBuffer.wrap(array)..releasedBy(arena);
+      expect(buffer, testDataBuffer(arena));
+    });
+  });
+
+  testRunner('wrap partial array', () {
+    using((arena) {
+      final array = JArray(jbyte.type, 3)..releasedBy(arena);
+      array[0] = 1;
+      array[1] = 2;
+      array[2] = 3;
+      final buffer = JByteBuffer.wrap(array, 1, 1)..releasedBy(arena);
+      expect(buffer.nextByte, 2);
+      expect(() => buffer.nextByte, throwsAJniException);
+    });
+  });
+
+  testRunner('capacity', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      expect(buffer.capacity, 3);
+    });
+  });
+
+  testRunner('position', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      expect(buffer.position, 0);
+      buffer.position = 2;
+      expect(buffer.position, 2);
+    });
+  });
+
+  testRunner('limit', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      expect(buffer.limit, 3);
+      buffer.limit = 2;
+      expect(buffer.limit, 2);
+    });
+  });
+
+  testRunner('mark and reset', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      buffer.position = 1;
+      buffer.mark();
+      buffer.position = 2;
+      buffer.reset();
+      expect(buffer.position, 1);
+    });
+  });
+
+  testRunner('clear', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      buffer.position = 2;
+      buffer.limit = 2;
+      buffer.clear();
+      expect(buffer.limit, 3);
+      expect(buffer.position, 0);
+    });
+  });
+
+  testRunner('flip', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      buffer.position = 2;
+      buffer.flip();
+      expect(buffer.limit, 2);
+      expect(buffer.position, 0);
+    });
+  });
+
+  testRunner('rewind', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      buffer.mark();
+      buffer.position = 2;
+      buffer.rewind();
+      expect(buffer.position, 0);
+      expect(buffer.reset, throwsAJniException);
+    });
+  });
+
+  testRunner('remaining and hasRemaining', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      buffer.position = 2;
+      expect(buffer.remaining, 1);
+      expect(buffer.hasRemaining, true);
+      buffer.position = 3;
+      expect(buffer.remaining, 0);
+      expect(buffer.hasRemaining, false);
+    });
+  });
+
+  testRunner('isReadOnly and asReadOnlyBuffer', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      expect(buffer.isReadOnly, false);
+      final readOnly = buffer.asReadOnlyBuffer()..releasedBy(arena);
+      expect(readOnly.isReadOnly, true);
+    });
+  });
+
+  testRunner('hasArray, array and arrayOffset', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      expect(buffer.hasArray, true);
+      expect(buffer.arrayOffset, 0);
+      expect(buffer.array.length, 3);
+      final directBuffer = JByteBuffer.allocateDirect(3)..releasedBy(arena);
+      expect(directBuffer.hasArray, false);
+    });
+  });
+
+  testRunner('isDirect', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      expect(buffer.isDirect, false);
+      final directBuffer = JByteBuffer.allocateDirect(3)..releasedBy(arena);
+      expect(directBuffer.isDirect, true);
+    });
+  });
+
+  testRunner('slice', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      buffer.position = 1;
+      buffer.limit = 2;
+      final sliced = buffer.slice()..releasedBy(arena);
+      expect(sliced.capacity, 1);
+      expect(sliced.nextByte, 2);
+    });
+  });
+
+  testRunner('duplicate', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      buffer.position = 1;
+      buffer.limit = 2;
+      final duplicate = buffer.duplicate()..releasedBy(arena);
+      expect(duplicate.capacity, 3);
+      expect(duplicate.position, 1);
+      expect(duplicate.limit, 2);
+    });
+  });
+
+  testRunner('asUint8List', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      expect(buffer.asUint8List, throwsA(isA<StateError>()));
+      final list = Uint8List.fromList([1, 2, 3]);
+      final directBuffer = list.toJByteBuffer()..releasedBy(arena);
+      expect(directBuffer.asUint8List(), list);
+    });
+  });
+
+  testRunner('type hashCode, ==', () {
+    using((arena) {
+      final a = testDataBuffer(arena);
+      final b = testDataBuffer(arena);
+      expect(a.$type, b.$type);
+      expect(a.$type.hashCode, b.$type.hashCode);
+      final c = JBuffer.fromRef(nullptr);
+      final d = JBuffer.fromRef(nullptr);
+      expect(c.$type, d.$type);
+      expect(c.$type.hashCode, d.$type.hashCode);
+
+      expect(a.$type, isNot(c.$type));
+      expect(a.$type.hashCode, isNot(c.$type.hashCode));
+    });
+  });
+
+  testRunner('asUint8List releasing original', () {
+    using((arena) {
+      // Used as an example in [JByteBuffer].
+      final directBuffer = JByteBuffer.allocateDirect(3);
+      final data1 = directBuffer.asUint8List();
+      directBuffer.nextByte = 42; // No problem!
+      expect(data1[0], 42);
+      final data2 = directBuffer.asUint8List(releaseOriginal: true);
+      expect(
+        () => directBuffer.nextByte = 42,
+        throwsA(isA<UseAfterReleaseException>()),
+      );
+      expect(data2[0], 42);
+    });
+  });
+}
diff --git a/pkgs/jni/test/type_test.dart b/pkgs/jni/test/type_test.dart
index f0f3caa..0ad8490 100644
--- a/pkgs/jni/test/type_test.dart
+++ b/pkgs/jni/test/type_test.dart
@@ -25,7 +25,7 @@
   JObjType<JObject> get $type => $AType();
 }
 
-class $AType extends JObjType<A> {
+final class $AType extends JObjType<A> {
   @override
   A fromRef(Pointer<Void> ref) {
     return A.fromRef(ref);
@@ -55,7 +55,7 @@
   JObjType<JObject> get $type => $BType();
 }
 
-class $BType extends JObjType<B> {
+final class $BType extends JObjType<B> {
   @override
   B fromRef(Pointer<Void> ref) {
     return B.fromRef(ref);
@@ -86,7 +86,7 @@
   JObjType<JObject> get $type => $CType();
 }
 
-class $CType extends JObjType<C> {
+final class $CType extends JObjType<C> {
   @override
   C fromRef(Pointer<Void> ref) {
     return C.fromRef(ref);
@@ -117,7 +117,7 @@
   JObjType<JObject> get $type => $DType();
 }
 
-class $DType extends JObjType<D> {
+final class $DType extends JObjType<D> {
   @override
   D fromRef(Pointer<Void> ref) {
     return D.fromRef(ref);
@@ -148,7 +148,7 @@
   JObjType<JObject> get $type => $EType();
 }
 
-class $EType extends JObjType<E> {
+final class $EType extends JObjType<E> {
   @override
   E fromRef(Pointer<Void> ref) {
     return E.fromRef(ref);
@@ -179,7 +179,7 @@
   JObjType<JObject> get $type => $FType();
 }
 
-class $FType extends JObjType<F> {
+final class $FType extends JObjType<F> {
   @override
   F fromRef(Pointer<Void> ref) {
     return F.fromRef(ref);
@@ -292,6 +292,13 @@
         ]),
         JObject.type,
       );
+      expect(
+        lowestCommonSuperType([
+          JByteBuffer.type,
+          JBuffer.type,
+        ]),
+        JBuffer.type,
+      );
     });
   });
 
diff --git a/pkgs/jnigen/CHANGELOG.md b/pkgs/jnigen/CHANGELOG.md
index 7e18e40..00b2fc1 100644
--- a/pkgs/jnigen/CHANGELOG.md
+++ b/pkgs/jnigen/CHANGELOG.md
@@ -1,41 +1,74 @@
-## 0.6.1-wip
-* Add `ignore_for_file: lines_longer_than_80_chars` to the generated file preamble.
-* Added an explicit cast in generated `<Interface>.implement` code to allow `dart analyze` to pass when `strict-casts` is set.
+## 0.7.0
+
+- **Breaking Change** ([#387](https://github.com/dart-lang/jnigen/issues/387)):
+  Added `JBuffer` and `JByteBuffer` classes as default classes for
+  `java.nio.Buffer` and `java.nio.ByteBuffer` respectively.
+- **Breaking Change**: Made the type classes `final`.
+- Added `ignore_for_file: lines_longer_than_80_chars` to the generated file
+  preamble.
+- Added an explicit cast in generated `<Interface>.implement` code to allow
+  `dart analyze` to pass when `strict-casts` is set.
 
 ## 0.6.0
-* **Breaking Change** ([#131](https://github.com/dart-lang/jnigen/issues/131)): Renamed `delete*` to `release*`.
-* **Breaking Change** ([#354](https://github.com/dart-lang/jnigen/issues/354)): Renamed constructors from `ctor1`, `ctor2`, ... to `new1`, `new2`, ...
-* **Breaking Change**: Specifying a class always pulls in nested classes by default. If a nested class is specified in config, it will be an error.
-* **Breaking Change**: Removed `suspend_fun_to_async` flag from the config. It's now happening by default since we read the Kotlin's metadata and reliably identify the `suspend fun`s.
-* Fixed a bug where the nested classes would be generated incorrectly depending on the backend used for generation.
-* Fixed a bug where ASM backend would produce the incorrect parent for multi-level nested classes.
-* Fixed a bug where the backends would produce different descriptors for the same method.
-* Added `enable_experiment` option to config.
-* Created an experiment called `interface_implementation` which creates a `.implement` method for interfaces, so you can implement them using Dart.
-* Save all `jnigen` logs to a file in `.dart_tool/jnigen/logs/`. This is useful for debugging.
+
+- **Breaking Change** ([#131](https://github.com/dart-lang/jnigen/issues/131)):
+  Renamed `delete*` to `release*`.
+- **Breaking Change** ([#354](https://github.com/dart-lang/jnigen/issues/354)):
+  Renamed constructors from `ctor1`, `ctor2`, ... to `new1`, `new2`, ...
+- **Breaking Change**: Specifying a class always pulls in nested classes by
+  default. If a nested class is specified in config, it will be an error.
+- **Breaking Change**: Removed `suspend_fun_to_async` flag from the config. It's
+  now happening by default since we read the Kotlin's metadata and reliably
+  identify the `suspend fun`s.
+- Fixed a bug where the nested classes would be generated incorrectly depending
+  on the backend used for generation.
+- Fixed a bug where ASM backend would produce the incorrect parent for
+  multi-level nested classes.
+- Fixed a bug where the backends would produce different descriptors for the
+  same method.
+- Added `enable_experiment` option to config.
+- Created an experiment called `interface_implementation` which creates a
+  `.implement` method for interfaces, so you can implement them using Dart.
+- Save all `jnigen` logs to a file in `.dart_tool/jnigen/logs/`. This is useful
+  for debugging.
 
 ## 0.5.0
-* **Breaking Change** ([#72](https://github.com/dart-lang/jnigen/issues/72)): Removed support for `importMap` in favor of the newly added interop mechanism with importing yaml files.
-* **Breaking Change** ([#72](https://github.com/dart-lang/jnigen/issues/72)): `java.util.Set`, `java.util.Map`, `java.util.List`, `java.util.Iterator` and the boxed types like `java.lang.Integer`, `java.lang.Double`, ... will be generated as their corresponding classes in `package:jni`.
-* Strings now use UTF16.
+
+- **Breaking Change** ([#72](https://github.com/dart-lang/jnigen/issues/72)):
+  Removed support for `importMap` in favor of the newly added interop mechanism
+  with importing yaml files.
+- **Breaking Change** ([#72](https://github.com/dart-lang/jnigen/issues/72)):
+  `java.util.Set`, `java.util.Map`, `java.util.List`, `java.util.Iterator` and
+  the boxed types like `java.lang.Integer`, `java.lang.Double`, ... will be
+  generated as their corresponding classes in `package:jni`.
+- Strings now use UTF16.
 
 ## 0.4.0
-* **Breaking Change** ([#145](https://github.com/dart-lang/jnigen/issues/145)): Type arguments are now named instead of positional.
-* Type parameters can now be inferred when possible.
-* Fixed a bug where passing a `long` argument truncated it to `int` in pure dart bindings.
-* Removed array extensions from the generated code.
-* Added the ability to use source dependencies from Gradle.
-* Fixed an issue with the field setter.
-* Fixed an issue where exceptions were not properly thrown in pure Dart bindings.
+
+- **Breaking Change** ([#145](https://github.com/dart-lang/jnigen/issues/145)):
+  Type arguments are now named instead of positional.
+- Type parameters can now be inferred when possible.
+- Fixed a bug where passing a `long` argument truncated it to `int` in pure dart
+  bindings.
+- Removed array extensions from the generated code.
+- Added the ability to use source dependencies from Gradle.
+- Fixed an issue with the field setter.
+- Fixed an issue where exceptions were not properly thrown in pure Dart
+  bindings.
 
 ## 0.3.0
-* Added the option to convert Kotlin `suspend fun` to Dart async methods. Add `suspend_fun_to_async: true` to `jnigen.yaml`.
+
+- Added the option to convert Kotlin `suspend fun` to Dart async methods. Add
+  `suspend_fun_to_async: true` to `jnigen.yaml`.
 
 ## 0.2.0
-* Support generating bindings for generics.
+
+- Support generating bindings for generics.
 
 ## 0.1.1
-* Windows support for running tests and examples on development machines.
+
+- Windows support for running tests and examples on development machines.
 
 ## 0.1.0
-* Initial version: Basic bindings generation, maven and android utilities
+
+- Initial version: Basic bindings generation, maven and android utilities
diff --git a/pkgs/jnigen/example/in_app_java/lib/android_utils.dart b/pkgs/jnigen/example/in_app_java/lib/android_utils.dart
index ec72286..8a0e58a 100644
--- a/pkgs/jnigen/example/in_app_java/lib/android_utils.dart
+++ b/pkgs/jnigen/example/in_app_java/lib/android_utils.dart
@@ -55,7 +55,7 @@
   }
 }
 
-class $AndroidUtilsType extends jni.JObjType<AndroidUtils> {
+final class $AndroidUtilsType extends jni.JObjType<AndroidUtils> {
   const $AndroidUtilsType();
 
   @override
@@ -1027,7 +1027,7 @@
   }
 }
 
-class $EmojiCompatType extends jni.JObjType<EmojiCompat> {
+final class $EmojiCompatType extends jni.JObjType<EmojiCompat> {
   const $EmojiCompatType();
 
   @override
@@ -1361,7 +1361,7 @@
   }
 }
 
-class $EmojiCompat_ConfigType extends jni.JObjType<EmojiCompat_Config> {
+final class $EmojiCompat_ConfigType extends jni.JObjType<EmojiCompat_Config> {
   const $EmojiCompat_ConfigType();
 
   @override
@@ -1450,7 +1450,7 @@
   }
 }
 
-class $EmojiCompat_MetadataRepoLoaderCallbackType
+final class $EmojiCompat_MetadataRepoLoaderCallbackType
     extends jni.JObjType<EmojiCompat_MetadataRepoLoaderCallback> {
   const $EmojiCompat_MetadataRepoLoaderCallbackType();
 
@@ -1551,7 +1551,7 @@
   }
 }
 
-class $EmojiCompat_GlyphCheckerType
+final class $EmojiCompat_GlyphCheckerType
     extends jni.JObjType<EmojiCompat_GlyphChecker> {
   const $EmojiCompat_GlyphCheckerType();
 
@@ -1613,7 +1613,7 @@
   }
 }
 
-class $EmojiCompat_MetadataRepoLoaderType
+final class $EmojiCompat_MetadataRepoLoaderType
     extends jni.JObjType<EmojiCompat_MetadataRepoLoader> {
   const $EmojiCompat_MetadataRepoLoaderType();
 
@@ -1697,7 +1697,7 @@
   }
 }
 
-class $EmojiCompat_InitCallbackType
+final class $EmojiCompat_InitCallbackType
     extends jni.JObjType<EmojiCompat_InitCallback> {
   const $EmojiCompat_InitCallbackType();
 
@@ -1771,7 +1771,7 @@
   }
 }
 
-class $EmojiCompat_DefaultSpanFactoryType
+final class $EmojiCompat_DefaultSpanFactoryType
     extends jni.JObjType<EmojiCompat_DefaultSpanFactory> {
   const $EmojiCompat_DefaultSpanFactoryType();
 
@@ -1841,7 +1841,7 @@
   }
 }
 
-class $EmojiCompat_SpanFactoryType
+final class $EmojiCompat_SpanFactoryType
     extends jni.JObjType<EmojiCompat_SpanFactory> {
   const $EmojiCompat_SpanFactoryType();
 
@@ -1939,7 +1939,7 @@
   }
 }
 
-class $DefaultEmojiCompatConfigType
+final class $DefaultEmojiCompatConfigType
     extends jni.JObjType<DefaultEmojiCompatConfig> {
   const $DefaultEmojiCompatConfigType();
 
@@ -2017,7 +2017,7 @@
   }
 }
 
-class $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28Type
+final class $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28Type
     extends jni
     .JObjType<DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28> {
   const $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28Type();
@@ -2126,7 +2126,7 @@
   }
 }
 
-class $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19Type
+final class $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19Type
     extends jni
     .JObjType<DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19> {
   const $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19Type();
@@ -2264,7 +2264,8 @@
   }
 }
 
-class $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelperType extends jni
+final class $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelperType
+    extends jni
     .JObjType<DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper> {
   const $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelperType();
 
@@ -2353,7 +2354,8 @@
   }
 }
 
-class $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactoryType extends jni
+final class $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactoryType
+    extends jni
     .JObjType<DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory> {
   const $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactoryType();
 
@@ -2461,7 +2463,7 @@
   }
 }
 
-class $Build_PartitionType extends jni.JObjType<Build_Partition> {
+final class $Build_PartitionType extends jni.JObjType<Build_Partition> {
   const $Build_PartitionType();
 
   @override
@@ -2613,7 +2615,7 @@
   }
 }
 
-class $Build_VERSIONType extends jni.JObjType<Build_VERSION> {
+final class $Build_VERSIONType extends jni.JObjType<Build_VERSION> {
   const $Build_VERSIONType();
 
   @override
@@ -2763,7 +2765,7 @@
   }
 }
 
-class $Build_VERSION_CODESType extends jni.JObjType<Build_VERSION_CODES> {
+final class $Build_VERSION_CODESType extends jni.JObjType<Build_VERSION_CODES> {
   const $Build_VERSION_CODESType();
 
   @override
@@ -3112,7 +3114,7 @@
   }
 }
 
-class $BuildType extends jni.JObjType<Build> {
+final class $BuildType extends jni.JObjType<Build> {
   const $BuildType();
 
   @override
@@ -3624,7 +3626,7 @@
   }
 }
 
-class $HashMapType<$K extends jni.JObject, $V extends jni.JObject>
+final class $HashMapType<$K extends jni.JObject, $V extends jni.JObject>
     extends jni.JObjType<HashMap<$K, $V>> {
   final jni.JObjType<$K> K;
   final jni.JObjType<$V> V;
diff --git a/pkgs/jnigen/example/kotlin_plugin/lib/kotlin_bindings.dart b/pkgs/jnigen/example/kotlin_plugin/lib/kotlin_bindings.dart
index ed2d034..efd0351 100644
--- a/pkgs/jnigen/example/kotlin_plugin/lib/kotlin_bindings.dart
+++ b/pkgs/jnigen/example/kotlin_plugin/lib/kotlin_bindings.dart
@@ -71,7 +71,7 @@
   }
 }
 
-class $ExampleType extends jni.JObjType<Example> {
+final class $ExampleType extends jni.JObjType<Example> {
   const $ExampleType();
 
   @override
diff --git a/pkgs/jnigen/example/notification_plugin/lib/notifications.dart b/pkgs/jnigen/example/notification_plugin/lib/notifications.dart
index ebb2f59..9855d16 100644
--- a/pkgs/jnigen/example/notification_plugin/lib/notifications.dart
+++ b/pkgs/jnigen/example/notification_plugin/lib/notifications.dart
@@ -75,7 +75,7 @@
   }
 }
 
-class $NotificationsType extends jni.JObjType<Notifications> {
+final class $NotificationsType extends jni.JObjType<Notifications> {
   const $NotificationsType();
 
   @override
diff --git a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocument.dart b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocument.dart
index 03a9da2..b2c40a2 100644
--- a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocument.dart
+++ b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocument.dart
@@ -1510,7 +1510,7 @@
   }
 }
 
-class $PDDocumentType extends jni.JObjType<PDDocument> {
+final class $PDDocumentType extends jni.JObjType<PDDocument> {
   const $PDDocumentType();
 
   @override
diff --git a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocumentInformation.dart b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocumentInformation.dart
index d928d21..3c959e7 100644
--- a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocumentInformation.dart
+++ b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocumentInformation.dart
@@ -496,7 +496,8 @@
   }
 }
 
-class $PDDocumentInformationType extends jni.JObjType<PDDocumentInformation> {
+final class $PDDocumentInformationType
+    extends jni.JObjType<PDDocumentInformation> {
   const $PDDocumentInformationType();
 
   @override
diff --git a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/PDFTextStripper.dart b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/PDFTextStripper.dart
index b42cd9a..930240b 100644
--- a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/PDFTextStripper.dart
+++ b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/PDFTextStripper.dart
@@ -1398,7 +1398,7 @@
   }
 }
 
-class $PDFTextStripperType extends jni.JObjType<PDFTextStripper> {
+final class $PDFTextStripperType extends jni.JObjType<PDFTextStripper> {
   const $PDFTextStripperType();
 
   @override
diff --git a/pkgs/jnigen/lib/src/bindings/dart_generator.dart b/pkgs/jnigen/lib/src/bindings/dart_generator.dart
index d8fb60d..ed52373 100644
--- a/pkgs/jnigen/lib/src/bindings/dart_generator.dart
+++ b/pkgs/jnigen/lib/src/bindings/dart_generator.dart
@@ -589,7 +589,7 @@
         ? '($typeClassName).hashCode'
         : 'Object.hash($typeClassName, $hashCodeTypeClasses)';
     s.write('''
-class $typeClassName$typeParamsDef extends $_jType<$name$typeParamsCall> {
+final class $typeClassName$typeParamsDef extends $_jType<$name$typeParamsCall> {
   $typeClassesDef
 
   const $typeClassName(
diff --git a/pkgs/jnigen/pubspec.yaml b/pkgs/jnigen/pubspec.yaml
index d8d08f7..3fbd456 100644
--- a/pkgs/jnigen/pubspec.yaml
+++ b/pkgs/jnigen/pubspec.yaml
@@ -4,7 +4,7 @@
 
 name: jnigen
 description: A Dart bindings generator for Java and Kotlin that uses JNI under the hood to interop with Java virtual machine.
-version: 0.6.1-wip
+version: 0.7.0
 repository: https://github.com/dart-lang/jnigen/tree/main/jnigen
 
 environment:
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart
index e1f19fe..95ddfd5 100644
--- a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart
@@ -1854,7 +1854,7 @@
   }
 }
 
-class $JsonFactoryType extends jni.JObjType<JsonFactory> {
+final class $JsonFactoryType extends jni.JObjType<JsonFactory> {
   const $JsonFactoryType();
 
   @override
@@ -1969,7 +1969,7 @@
   }
 }
 
-class $JsonFactory_FeatureType extends jni.JObjType<JsonFactory_Feature> {
+final class $JsonFactory_FeatureType extends jni.JObjType<JsonFactory_Feature> {
   const $JsonFactory_FeatureType();
 
   @override
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart
index 1dac51c..5775da7 100644
--- a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart
@@ -2636,7 +2636,7 @@
   }
 }
 
-class $JsonParserType extends jni.JObjType<JsonParser> {
+final class $JsonParserType extends jni.JObjType<JsonParser> {
   const $JsonParserType();
 
   @override
@@ -2750,7 +2750,7 @@
   }
 }
 
-class $JsonParser_FeatureType extends jni.JObjType<JsonParser_Feature> {
+final class $JsonParser_FeatureType extends jni.JObjType<JsonParser_Feature> {
   const $JsonParser_FeatureType();
 
   @override
@@ -2818,7 +2818,8 @@
   }
 }
 
-class $JsonParser_NumberTypeType extends jni.JObjType<JsonParser_NumberType> {
+final class $JsonParser_NumberTypeType
+    extends jni.JObjType<JsonParser_NumberType> {
   const $JsonParser_NumberTypeType();
 
   @override
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart
index fdbd542..bab355e 100644
--- a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart
@@ -213,7 +213,7 @@
   }
 }
 
-class $JsonTokenType extends jni.JObjType<JsonToken> {
+final class $JsonTokenType extends jni.JObjType<JsonToken> {
   const $JsonTokenType();
 
   @override
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart
index f9f3631..3cdf2ff 100644
--- a/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart
@@ -1756,7 +1756,7 @@
   }
 }
 
-class $JsonFactoryType extends jni.JObjType<JsonFactory> {
+final class $JsonFactoryType extends jni.JObjType<JsonFactory> {
   const $JsonFactoryType();
 
   @override
@@ -1868,7 +1868,7 @@
   }
 }
 
-class $JsonFactory_FeatureType extends jni.JObjType<JsonFactory_Feature> {
+final class $JsonFactory_FeatureType extends jni.JObjType<JsonFactory_Feature> {
   const $JsonFactory_FeatureType();
 
   @override
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart
index ae607a9..d94c220 100644
--- a/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart
@@ -2489,7 +2489,7 @@
   }
 }
 
-class $JsonParserType extends jni.JObjType<JsonParser> {
+final class $JsonParserType extends jni.JObjType<JsonParser> {
   const $JsonParserType();
 
   @override
@@ -2600,7 +2600,7 @@
   }
 }
 
-class $JsonParser_FeatureType extends jni.JObjType<JsonParser_Feature> {
+final class $JsonParser_FeatureType extends jni.JObjType<JsonParser_Feature> {
   const $JsonParser_FeatureType();
 
   @override
@@ -2672,7 +2672,8 @@
   }
 }
 
-class $JsonParser_NumberTypeType extends jni.JObjType<JsonParser_NumberType> {
+final class $JsonParser_NumberTypeType
+    extends jni.JObjType<JsonParser_NumberType> {
   const $JsonParser_NumberTypeType();
 
   @override
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart
index da0a49e..573a815 100644
--- a/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart
@@ -199,7 +199,7 @@
   }
 }
 
-class $JsonTokenType extends jni.JObjType<JsonToken> {
+final class $JsonTokenType extends jni.JObjType<JsonToken> {
   const $JsonTokenType();
 
   @override
diff --git a/pkgs/jnigen/test/kotlin_test/c_based/dart_bindings/kotlin.dart b/pkgs/jnigen/test/kotlin_test/c_based/dart_bindings/kotlin.dart
index 5b80bb8..3e26427 100644
--- a/pkgs/jnigen/test/kotlin_test/c_based/dart_bindings/kotlin.dart
+++ b/pkgs/jnigen/test/kotlin_test/c_based/dart_bindings/kotlin.dart
@@ -102,7 +102,7 @@
   }
 }
 
-class $SuspendFunType extends jni.JObjType<SuspendFun> {
+final class $SuspendFunType extends jni.JObjType<SuspendFun> {
   const $SuspendFunType();
 
   @override
diff --git a/pkgs/jnigen/test/kotlin_test/dart_only/dart_bindings/kotlin.dart b/pkgs/jnigen/test/kotlin_test/dart_only/dart_bindings/kotlin.dart
index c352dfb..bd4441f 100644
--- a/pkgs/jnigen/test/kotlin_test/dart_only/dart_bindings/kotlin.dart
+++ b/pkgs/jnigen/test/kotlin_test/dart_only/dart_bindings/kotlin.dart
@@ -92,7 +92,7 @@
   }
 }
 
-class $SuspendFunType extends jni.JObjType<SuspendFun> {
+final class $SuspendFunType extends jni.JObjType<SuspendFun> {
   const $SuspendFunType();
 
   @override
diff --git a/pkgs/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart b/pkgs/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart
index 2879e35..1142fd6 100644
--- a/pkgs/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart
+++ b/pkgs/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart
@@ -66,7 +66,7 @@
   }
 }
 
-class $ColorType extends jni.JObjType<Color> {
+final class $ColorType extends jni.JObjType<Color> {
   const $ColorType();
 
   @override
@@ -779,7 +779,7 @@
   }
 }
 
-class $ExampleType extends jni.JObjType<Example> {
+final class $ExampleType extends jni.JObjType<Example> {
   const $ExampleType();
 
   @override
@@ -864,7 +864,7 @@
   }
 }
 
-class $Example_NestedType extends jni.JObjType<Example_Nested> {
+final class $Example_NestedType extends jni.JObjType<Example_Nested> {
   const $Example_NestedType();
 
   @override
@@ -928,7 +928,7 @@
   }
 }
 
-class $Example_Nested_NestedTwiceType
+final class $Example_Nested_NestedTwiceType
     extends jni.JObjType<Example_Nested_NestedTwice> {
   const $Example_Nested_NestedTwiceType();
 
@@ -1004,7 +1004,7 @@
   }
 }
 
-class $Example_NonStaticNestedType
+final class $Example_NonStaticNestedType
     extends jni.JObjType<Example_NonStaticNested> {
   const $Example_NonStaticNestedType();
 
@@ -1249,7 +1249,7 @@
   }
 }
 
-class $ExceptionsType extends jni.JObjType<Exceptions> {
+final class $ExceptionsType extends jni.JObjType<Exceptions> {
   const $ExceptionsType();
 
   @override
@@ -1504,7 +1504,7 @@
   }
 }
 
-class $FieldsType extends jni.JObjType<Fields> {
+final class $FieldsType extends jni.JObjType<Fields> {
   const $FieldsType();
 
   @override
@@ -1594,7 +1594,7 @@
   }
 }
 
-class $Fields_NestedType extends jni.JObjType<Fields_Nested> {
+final class $Fields_NestedType extends jni.JObjType<Fields_Nested> {
   const $Fields_NestedType();
 
   @override
@@ -1658,7 +1658,7 @@
   }
 }
 
-class $C2Type extends jni.JObjType<C2> {
+final class $C2Type extends jni.JObjType<C2> {
   const $C2Type();
 
   @override
@@ -1715,7 +1715,7 @@
   }
 }
 
-class $Example1Type extends jni.JObjType<Example1> {
+final class $Example1Type extends jni.JObjType<Example1> {
   const $Example1Type();
 
   @override
@@ -1780,8 +1780,8 @@
   }
 }
 
-class $GenericTypeParamsType<$S extends jni.JObject, $K extends jni.JObject>
-    extends jni.JObjType<GenericTypeParams<$S, $K>> {
+final class $GenericTypeParamsType<$S extends jni.JObject,
+    $K extends jni.JObject> extends jni.JObjType<GenericTypeParams<$S, $K>> {
   final jni.JObjType<$S> S;
   final jni.JObjType<$K> K;
 
@@ -1959,7 +1959,7 @@
   }
 }
 
-class $GrandParentType<$T extends jni.JObject>
+final class $GrandParentType<$T extends jni.JObject>
     extends jni.JObjType<GrandParent<$T>> {
   final jni.JObjType<$T> T;
 
@@ -2096,8 +2096,8 @@
   }
 }
 
-class $GrandParent_ParentType<$T extends jni.JObject, $S extends jni.JObject>
-    extends jni.JObjType<GrandParent_Parent<$T, $S>> {
+final class $GrandParent_ParentType<$T extends jni.JObject,
+    $S extends jni.JObject> extends jni.JObjType<GrandParent_Parent<$T, $S>> {
   final jni.JObjType<$T> T;
   final jni.JObjType<$S> S;
 
@@ -2275,7 +2275,7 @@
   }
 }
 
-class $GrandParent_Parent_ChildType<$T extends jni.JObject,
+final class $GrandParent_Parent_ChildType<$T extends jni.JObject,
         $S extends jni.JObject, $U extends jni.JObject>
     extends jni.JObjType<GrandParent_Parent_Child<$T, $S, $U>> {
   final jni.JObjType<$T> T;
@@ -2381,7 +2381,7 @@
   }
 }
 
-class $GrandParent_StaticParentType<$S extends jni.JObject>
+final class $GrandParent_StaticParentType<$S extends jni.JObject>
     extends jni.JObjType<GrandParent_StaticParent<$S>> {
   final jni.JObjType<$S> S;
 
@@ -2528,7 +2528,7 @@
   }
 }
 
-class $GrandParent_StaticParent_ChildType<$S extends jni.JObject,
+final class $GrandParent_StaticParent_ChildType<$S extends jni.JObject,
         $U extends jni.JObject>
     extends jni.JObjType<GrandParent_StaticParent_Child<$S, $U>> {
   final jni.JObjType<$S> S;
@@ -2653,7 +2653,7 @@
   }
 }
 
-class $MyMapType<$K extends jni.JObject, $V extends jni.JObject>
+final class $MyMapType<$K extends jni.JObject, $V extends jni.JObject>
     extends jni.JObjType<MyMap<$K, $V>> {
   final jni.JObjType<$K> K;
   final jni.JObjType<$V> V;
@@ -2796,7 +2796,7 @@
   }
 }
 
-class $MyMap_MyEntryType<$K extends jni.JObject, $V extends jni.JObject>
+final class $MyMap_MyEntryType<$K extends jni.JObject, $V extends jni.JObject>
     extends jni.JObjType<MyMap_MyEntry<$K, $V>> {
   final jni.JObjType<$K> K;
   final jni.JObjType<$V> V;
@@ -2996,7 +2996,8 @@
   }
 }
 
-class $MyStackType<$T extends jni.JObject> extends jni.JObjType<MyStack<$T>> {
+final class $MyStackType<$T extends jni.JObject>
+    extends jni.JObjType<MyStack<$T>> {
   final jni.JObjType<$T> T;
 
   const $MyStackType(
@@ -3060,7 +3061,7 @@
   }
 }
 
-class $StringKeyedMapType<$V extends jni.JObject>
+final class $StringKeyedMapType<$V extends jni.JObject>
     extends jni.JObjType<StringKeyedMap<$V>> {
   final jni.JObjType<$V> V;
 
@@ -3115,7 +3116,7 @@
   }
 }
 
-class $StringMapType extends jni.JObjType<StringMap> {
+final class $StringMapType extends jni.JObjType<StringMap> {
   const $StringMapType();
 
   @override
@@ -3161,7 +3162,7 @@
   }
 }
 
-class $StringStackType extends jni.JObjType<StringStack> {
+final class $StringStackType extends jni.JObjType<StringStack> {
   const $StringStackType();
 
   @override
@@ -3219,7 +3220,7 @@
   }
 }
 
-class $StringValuedMapType<$K extends jni.JObject>
+final class $StringValuedMapType<$K extends jni.JObject>
     extends jni.JObjType<StringValuedMap<$K>> {
   final jni.JObjType<$K> K;
 
@@ -3500,7 +3501,7 @@
   }
 }
 
-class $MyInterfaceType<$T extends jni.JObject>
+final class $MyInterfaceType<$T extends jni.JObject>
     extends jni.JObjType<MyInterface<$T>> {
   final jni.JObjType<$T> T;
 
@@ -3624,7 +3625,7 @@
   }
 }
 
-class $MyInterfaceConsumerType extends jni.JObjType<MyInterfaceConsumer> {
+final class $MyInterfaceConsumerType extends jni.JObjType<MyInterfaceConsumer> {
   const $MyInterfaceConsumerType();
 
   @override
@@ -3762,7 +3763,7 @@
   }
 }
 
-class $MyRunnableType extends jni.JObjType<MyRunnable> {
+final class $MyRunnableType extends jni.JObjType<MyRunnable> {
   const $MyRunnableType();
 
   @override
@@ -3862,7 +3863,7 @@
   }
 }
 
-class $MyRunnableRunnerType extends jni.JObjType<MyRunnableRunner> {
+final class $MyRunnableRunnerType extends jni.JObjType<MyRunnableRunner> {
   const $MyRunnableRunnerType();
 
   @override
@@ -3927,7 +3928,8 @@
   }
 }
 
-class $JsonSerializable_CaseType extends jni.JObjType<JsonSerializable_Case> {
+final class $JsonSerializable_CaseType
+    extends jni.JObjType<JsonSerializable_Case> {
   const $JsonSerializable_CaseType();
 
   @override
@@ -3976,7 +3978,7 @@
   }
 }
 
-class $MyDataClassType extends jni.JObjType<MyDataClass> {
+final class $MyDataClassType extends jni.JObjType<MyDataClass> {
   const $MyDataClassType();
 
   @override
diff --git a/pkgs/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart b/pkgs/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart
index b549cc8..7ac848d 100644
--- a/pkgs/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart
+++ b/pkgs/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart
@@ -68,7 +68,7 @@
   }
 }
 
-class $ColorType extends jni.JObjType<Color> {
+final class $ColorType extends jni.JObjType<Color> {
   const $ColorType();
 
   @override
@@ -718,7 +718,7 @@
   }
 }
 
-class $ExampleType extends jni.JObjType<Example> {
+final class $ExampleType extends jni.JObjType<Example> {
   const $ExampleType();
 
   @override
@@ -799,7 +799,7 @@
   }
 }
 
-class $Example_NestedType extends jni.JObjType<Example_Nested> {
+final class $Example_NestedType extends jni.JObjType<Example_Nested> {
   const $Example_NestedType();
 
   @override
@@ -865,7 +865,7 @@
   }
 }
 
-class $Example_Nested_NestedTwiceType
+final class $Example_Nested_NestedTwiceType
     extends jni.JObjType<Example_Nested_NestedTwice> {
   const $Example_Nested_NestedTwiceType();
 
@@ -935,7 +935,7 @@
   }
 }
 
-class $Example_NonStaticNestedType
+final class $Example_NonStaticNestedType
     extends jni.JObjType<Example_NonStaticNested> {
   const $Example_NonStaticNestedType();
 
@@ -1172,7 +1172,7 @@
   }
 }
 
-class $ExceptionsType extends jni.JObjType<Exceptions> {
+final class $ExceptionsType extends jni.JObjType<Exceptions> {
   const $ExceptionsType();
 
   @override
@@ -1383,7 +1383,7 @@
   }
 }
 
-class $FieldsType extends jni.JObjType<Fields> {
+final class $FieldsType extends jni.JObjType<Fields> {
   const $FieldsType();
 
   @override
@@ -1467,7 +1467,7 @@
   }
 }
 
-class $Fields_NestedType extends jni.JObjType<Fields_Nested> {
+final class $Fields_NestedType extends jni.JObjType<Fields_Nested> {
   const $Fields_NestedType();
 
   @override
@@ -1533,7 +1533,7 @@
   }
 }
 
-class $C2Type extends jni.JObjType<C2> {
+final class $C2Type extends jni.JObjType<C2> {
   const $C2Type();
 
   @override
@@ -1591,7 +1591,7 @@
   }
 }
 
-class $Example1Type extends jni.JObjType<Example1> {
+final class $Example1Type extends jni.JObjType<Example1> {
   const $Example1Type();
 
   @override
@@ -1662,8 +1662,8 @@
   }
 }
 
-class $GenericTypeParamsType<$S extends jni.JObject, $K extends jni.JObject>
-    extends jni.JObjType<GenericTypeParams<$S, $K>> {
+final class $GenericTypeParamsType<$S extends jni.JObject,
+    $K extends jni.JObject> extends jni.JObjType<GenericTypeParams<$S, $K>> {
   final jni.JObjType<$S> S;
   final jni.JObjType<$K> K;
 
@@ -1835,7 +1835,7 @@
   }
 }
 
-class $GrandParentType<$T extends jni.JObject>
+final class $GrandParentType<$T extends jni.JObject>
     extends jni.JObjType<GrandParent<$T>> {
   final jni.JObjType<$T> T;
 
@@ -1957,8 +1957,8 @@
   }
 }
 
-class $GrandParent_ParentType<$T extends jni.JObject, $S extends jni.JObject>
-    extends jni.JObjType<GrandParent_Parent<$T, $S>> {
+final class $GrandParent_ParentType<$T extends jni.JObject,
+    $S extends jni.JObject> extends jni.JObjType<GrandParent_Parent<$T, $S>> {
   final jni.JObjType<$T> T;
   final jni.JObjType<$S> S;
 
@@ -2111,7 +2111,7 @@
   }
 }
 
-class $GrandParent_Parent_ChildType<$T extends jni.JObject,
+final class $GrandParent_Parent_ChildType<$T extends jni.JObject,
         $S extends jni.JObject, $U extends jni.JObject>
     extends jni.JObjType<GrandParent_Parent_Child<$T, $S, $U>> {
   final jni.JObjType<$T> T;
@@ -2211,7 +2211,7 @@
   }
 }
 
-class $GrandParent_StaticParentType<$S extends jni.JObject>
+final class $GrandParent_StaticParentType<$S extends jni.JObject>
     extends jni.JObjType<GrandParent_StaticParent<$S>> {
   final jni.JObjType<$S> S;
 
@@ -2341,7 +2341,7 @@
   }
 }
 
-class $GrandParent_StaticParent_ChildType<$S extends jni.JObject,
+final class $GrandParent_StaticParent_ChildType<$S extends jni.JObject,
         $U extends jni.JObject>
     extends jni.JObjType<GrandParent_StaticParent_Child<$S, $U>> {
   final jni.JObjType<$S> S;
@@ -2464,7 +2464,7 @@
   }
 }
 
-class $MyMapType<$K extends jni.JObject, $V extends jni.JObject>
+final class $MyMapType<$K extends jni.JObject, $V extends jni.JObject>
     extends jni.JObjType<MyMap<$K, $V>> {
   final jni.JObjType<$K> K;
   final jni.JObjType<$V> V;
@@ -2592,7 +2592,7 @@
   }
 }
 
-class $MyMap_MyEntryType<$K extends jni.JObject, $V extends jni.JObject>
+final class $MyMap_MyEntryType<$K extends jni.JObject, $V extends jni.JObject>
     extends jni.JObjType<MyMap_MyEntry<$K, $V>> {
   final jni.JObjType<$K> K;
   final jni.JObjType<$V> V;
@@ -2797,7 +2797,8 @@
   }
 }
 
-class $MyStackType<$T extends jni.JObject> extends jni.JObjType<MyStack<$T>> {
+final class $MyStackType<$T extends jni.JObject>
+    extends jni.JObjType<MyStack<$T>> {
   final jni.JObjType<$T> T;
 
   const $MyStackType(
@@ -2866,7 +2867,7 @@
   }
 }
 
-class $StringKeyedMapType<$V extends jni.JObject>
+final class $StringKeyedMapType<$V extends jni.JObject>
     extends jni.JObjType<StringKeyedMap<$V>> {
   final jni.JObjType<$V> V;
 
@@ -2924,7 +2925,7 @@
   }
 }
 
-class $StringMapType extends jni.JObjType<StringMap> {
+final class $StringMapType extends jni.JObjType<StringMap> {
   const $StringMapType();
 
   @override
@@ -2973,7 +2974,7 @@
   }
 }
 
-class $StringStackType extends jni.JObjType<StringStack> {
+final class $StringStackType extends jni.JObjType<StringStack> {
   const $StringStackType();
 
   @override
@@ -3036,7 +3037,7 @@
   }
 }
 
-class $StringValuedMapType<$K extends jni.JObject>
+final class $StringValuedMapType<$K extends jni.JObject>
     extends jni.JObjType<StringValuedMap<$K>> {
   final jni.JObjType<$K> K;
 
@@ -3309,7 +3310,7 @@
   }
 }
 
-class $MyInterfaceType<$T extends jni.JObject>
+final class $MyInterfaceType<$T extends jni.JObject>
     extends jni.JObjType<MyInterface<$T>> {
   final jni.JObjType<$T> T;
 
@@ -3430,7 +3431,7 @@
   }
 }
 
-class $MyInterfaceConsumerType extends jni.JObjType<MyInterfaceConsumer> {
+final class $MyInterfaceConsumerType extends jni.JObjType<MyInterfaceConsumer> {
   const $MyInterfaceConsumerType();
 
   @override
@@ -3569,7 +3570,7 @@
   }
 }
 
-class $MyRunnableType extends jni.JObjType<MyRunnable> {
+final class $MyRunnableType extends jni.JObjType<MyRunnable> {
   const $MyRunnableType();
 
   @override
@@ -3656,7 +3657,7 @@
   }
 }
 
-class $MyRunnableRunnerType extends jni.JObjType<MyRunnableRunner> {
+final class $MyRunnableRunnerType extends jni.JObjType<MyRunnableRunner> {
   const $MyRunnableRunnerType();
 
   @override
@@ -3725,7 +3726,8 @@
   }
 }
 
-class $JsonSerializable_CaseType extends jni.JObjType<JsonSerializable_Case> {
+final class $JsonSerializable_CaseType
+    extends jni.JObjType<JsonSerializable_Case> {
   const $JsonSerializable_CaseType();
 
   @override
@@ -3777,7 +3779,7 @@
   }
 }
 
-class $MyDataClassType extends jni.JObjType<MyDataClass> {
+final class $MyDataClassType extends jni.JObjType<MyDataClass> {
   const $MyDataClassType();
 
   @override