Remove Checker Framework; add NullAway, Picnic Error Prone Support.

Change-Id: I6558f0b4db0f9a192c18bbe45e2eaf10595bc666
diff --git a/jgvariant-core/pom.xml b/jgvariant-core/pom.xml
index f8c7b42..b37b65c 100644
--- a/jgvariant-core/pom.xml
+++ b/jgvariant-core/pom.xml
@@ -50,20 +50,6 @@
       <optional>true</optional>
     </dependency>
 
-    <!-- Static analysis -->
-    <dependency>
-      <groupId>org.checkerframework</groupId>
-      <artifactId>checker</artifactId>
-      <scope>provided</scope>
-      <optional>true</optional>
-    </dependency>
-    <dependency>
-      <groupId>org.checkerframework</groupId>
-      <artifactId>checker-qual</artifactId>
-      <scope>provided</scope>
-      <optional>true</optional>
-    </dependency>
-
     <!-- Testing -->
     <dependency>
       <groupId>org.junit.jupiter</groupId>
diff --git a/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java b/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java
index 2cb36c6..4538900 100644
--- a/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java
+++ b/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java
@@ -5,15 +5,16 @@
 package eu.mulk.jgvariant.core;
 
 import static java.nio.ByteOrder.LITTLE_ENDIAN;
+import static java.util.Objects.requireNonNullElse;
 import static java.util.stream.Collectors.toMap;
 
+import com.google.errorprone.annotations.Immutable;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.RecordComponent;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.charset.Charset;
 import java.text.ParseException;
-import java.util.AbstractMap.SimpleImmutableEntry;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -56,8 +57,9 @@
  *
  * @param <T> the type that the {@link Decoder} can decode.
  */
-@SuppressWarnings("java:S1610")
 @API(status = Status.EXPERIMENTAL)
+@Immutable
+@SuppressWarnings({"ImmutableListOf", "InvalidInlineTag", "java:S1610", "UnescapedEntity"})
 public abstract class Decoder<T> {
 
   private Decoder() {}
@@ -523,6 +525,7 @@
     }
   }
 
+  @SuppressWarnings("Immutable")
   private static class TupleDecoder extends Decoder<Object[]> {
 
     private final Decoder<?>[] componentDecoders;
@@ -625,7 +628,7 @@
     @SuppressWarnings("unchecked")
     public Map.@NotNull Entry<K, V> decode(ByteBuffer byteSlice) {
       Object[] components = tupleDecoder.decode(byteSlice);
-      return new SimpleImmutableEntry<>((K) components[0], (V) components[1]);
+      return Map.entry((K) components[0], (V) components[1]);
     }
   }
 
@@ -800,6 +803,7 @@
     }
   }
 
+  @SuppressWarnings("Immutable")
   private class MappingDecoder<U> extends Decoder<U> {
 
     private final Function<@NotNull T, @NotNull U> function;
@@ -824,6 +828,7 @@
     }
   }
 
+  @SuppressWarnings("Immutable")
   private class ContramappingDecoder extends Decoder<T> {
 
     private final UnaryOperator<ByteBuffer> function;
@@ -879,6 +884,7 @@
     return byteSlice.slice(index, length).order(byteSlice.order());
   }
 
+  @SuppressWarnings("Immutable")
   private static class PredicateDecoder<U> extends Decoder<U> {
 
     private final Predicate<ByteBuffer> selector;
@@ -900,8 +906,8 @@
         throw new IllegalArgumentException(
             "incompatible sizes in predicate branches: then=%s, else=%s"
                 .formatted(
-                    Objects.requireNonNullElse(thenDecoder.fixedSize(), "(null)"),
-                    Objects.requireNonNullElse(elseDecoder.fixedSize(), "(null)")));
+                    requireNonNullElse(thenDecoder.fixedSize(), "(null)"),
+                    requireNonNullElse(elseDecoder.fixedSize(), "(null)")));
       }
     }
 
diff --git a/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Signature.java b/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Signature.java
index cc9674d..4c8cd65 100644
--- a/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Signature.java
+++ b/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Signature.java
@@ -4,8 +4,10 @@
 
 package eu.mulk.jgvariant.core;
 
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.List;
@@ -41,7 +43,7 @@
     this.decoder = parseSignature(signatureBytes);
 
     signatureBytes.rewind();
-    this.signatureString = StandardCharsets.US_ASCII.decode(signatureBytes).toString();
+    this.signatureString = US_ASCII.decode(signatureBytes).toString();
   }
 
   static Signature parse(ByteBuffer signatureBytes) throws ParseException {
@@ -49,7 +51,7 @@
   }
 
   public static Signature parse(String signatureString) throws ParseException {
-    var signatureBytes = ByteBuffer.wrap(signatureString.getBytes(StandardCharsets.US_ASCII));
+    var signatureBytes = ByteBuffer.wrap(signatureString.getBytes(US_ASCII));
     return parse(signatureBytes);
   }
 
@@ -93,7 +95,7 @@
       case 'i', 'u' -> Decoder.ofInt();
       case 'x', 't' -> Decoder.ofLong();
       case 'd' -> Decoder.ofDouble();
-      case 's', 'o', 'g' -> Decoder.ofString(StandardCharsets.UTF_8);
+      case 's', 'o', 'g' -> Decoder.ofString(UTF_8);
       case 'v' -> Decoder.ofVariant();
       case 'm' -> Decoder.ofMaybe(parseSignature(signature));
       case '(' -> Decoder.ofStructure(parseTupleTypes(signature).toArray(new Decoder<?>[0]));
diff --git a/jgvariant-core/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java b/jgvariant-core/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java
index 3f826be..6399f6e 100644
--- a/jgvariant-core/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java
+++ b/jgvariant-core/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java
@@ -23,17 +23,26 @@
  * Tests based on the examples given in <a
  * href="https://people.gnome.org/~desrt/gvariant-serialisation.pdf">~desrt/gvariant-serialisation.pdf</a>.
  */
+@SuppressWarnings({
+  "ImmutableListOf",
+  "ImmutableListOf1",
+  "ImmutableListOf2",
+  "ImmutableListOf3",
+  "ImmutableListOf4",
+  "ImmutableListOf5",
+  "ImmutableMapOf2"
+})
 class DecoderTest {
 
   @Test
-  void testString() {
+  void string() {
     var data = new byte[] {0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x00};
     var decoder = Decoder.ofString(UTF_8);
     assertEquals("hello world", decoder.decode(ByteBuffer.wrap(data)));
   }
 
   @Test
-  void testMaybe() {
+  void maybe() {
     var data =
         new byte[] {0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x00, 0x00};
     var decoder = Decoder.ofMaybe(Decoder.ofString(UTF_8));
@@ -41,14 +50,14 @@
   }
 
   @Test
-  void testBooleanArray() {
+  void booleanArray() {
     var data = new byte[] {0x01, 0x00, 0x00, 0x01, 0x01};
     var decoder = Decoder.ofArray(Decoder.ofBoolean());
     assertEquals(List.of(true, false, false, true, true), decoder.decode(ByteBuffer.wrap(data)));
   }
 
   @Test
-  void testStructure() {
+  void structure() {
     var data =
         new byte[] {
           0x66, 0x6F, 0x6F, 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 0x04
@@ -61,7 +70,7 @@
   }
 
   @Test
-  void testComplexStructureArray() {
+  void complexStructureArray() {
     var data =
         new byte[] {
           0x68,
@@ -103,7 +112,7 @@
   }
 
   @Test
-  void testDictionary() {
+  void dictionary() {
     var data =
         new byte[] {
           0x68,
@@ -137,7 +146,7 @@
   }
 
   @Test
-  void testStringArray() {
+  void stringArray() {
     var data =
         new byte[] {
           0x69, 0x00, 0x63, 0x61, 0x6E, 0x00, 0x68, 0x61, 0x73, 0x00, 0x73, 0x74, 0x72, 0x69, 0x6E,
@@ -148,7 +157,7 @@
   }
 
   @Test
-  void testNestedStructure() {
+  void nestedStructure() {
     var data =
         new byte[] {
           0x69, 0x63, 0x61, 0x6E, 0x00, 0x68, 0x61, 0x73, 0x00, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67,
@@ -170,7 +179,7 @@
   }
 
   @Test
-  void testNestedStructureVariant() {
+  void nestedStructureVariant() {
     var data =
         new byte[] {
           0x69, 0x63, 0x61, 0x6E, 0x00, 0x68, 0x61, 0x73, 0x00, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67,
@@ -189,7 +198,7 @@
   }
 
   @Test
-  void testSimpleStructure() {
+  void simpleStructure() {
     var data = new byte[] {0x60, 0x70};
 
     record TestRecord(byte b1, byte b2) {}
@@ -204,7 +213,7 @@
   }
 
   @Test
-  void testPaddedStructureRight() {
+  void paddedStructureRight() {
     var data = new byte[] {0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00};
 
     record TestRecord(int b1, byte b2) {}
@@ -219,7 +228,7 @@
   }
 
   @Test
-  void testPaddedStructureLeft() {
+  void paddedStructureLeft() {
     var data = new byte[] {0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00};
 
     record TestRecord(byte b1, int b2) {}
@@ -234,7 +243,7 @@
   }
 
   @Test
-  void testSimpleStructureArray() {
+  void simpleStructureArray() {
     var data =
         new byte[] {
           0x60,
@@ -270,7 +279,7 @@
   }
 
   @Test
-  void testByteArray() {
+  void byteArray() {
     var data = new byte[] {0x04, 0x05, 0x06, 0x07};
 
     var decoder = Decoder.ofArray(Decoder.ofByte());
@@ -281,7 +290,7 @@
   }
 
   @Test
-  void testPrimitiveByteArray() {
+  void primitiveByteArray() {
     var data = new byte[] {0x04, 0x05, 0x06, 0x07};
 
     var decoder = Decoder.ofByteArray();
@@ -290,7 +299,7 @@
   }
 
   @Test
-  void testPrimitiveByteArrayRecord() {
+  void primitiveByteArrayRecord() {
     var data = new byte[] {0x04, 0x05, 0x06, 0x07};
 
     record TestRecord(byte[] bytes) {}
@@ -301,7 +310,7 @@
   }
 
   @Test
-  void testIntegerArray() {
+  void integerArray() {
     var data = new byte[] {0x04, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00};
 
     var decoder = Decoder.ofArray(Decoder.ofInt().withByteOrder(LITTLE_ENDIAN));
@@ -310,7 +319,7 @@
   }
 
   @Test
-  void testDictionaryEntryAsMapEntry() {
+  void dictionaryEntryAsMapEntry() {
     var data =
         new byte[] {0x61, 0x20, 0x6B, 0x65, 0x79, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x06};
 
@@ -321,7 +330,7 @@
   }
 
   @Test
-  void testDictionaryEntryAsRecord() {
+  void dictionaryEntryAsRecord() {
     var data =
         new byte[] {0x61, 0x20, 0x6B, 0x65, 0x79, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x06};
 
@@ -334,7 +343,7 @@
   }
 
   @Test
-  void testPaddedPrimitives() {
+  void paddedPrimitives() {
     var data =
         new byte[] {
           0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -353,7 +362,7 @@
   }
 
   @Test
-  void testEmbeddedMaybe() {
+  void embeddedMaybe() {
     var data = new byte[] {0x01, 0x01};
 
     record TestRecord(Optional<Byte> set, Optional<Byte> unset) {}
@@ -367,7 +376,7 @@
   }
 
   @Test
-  void testRecordComponentMismatch() {
+  void recordComponentMismatch() {
     record TestRecord(Optional<Byte> set) {}
 
     var maybeDecoder = Decoder.ofMaybe(Decoder.ofByte());
@@ -377,7 +386,7 @@
   }
 
   @Test
-  void testTrivialRecord() {
+  void trivialRecord() {
     var data = new byte[] {0x00};
 
     record TestRecord() {}
@@ -387,7 +396,7 @@
   }
 
   @Test
-  void testTwoElementTrivialRecordArray() {
+  void twoElementTrivialRecordArray() {
     var data = new byte[] {0x00, 0x00};
 
     record TestRecord() {}
@@ -398,7 +407,7 @@
   }
 
   @Test
-  void testSingletonTrivialRecordArray() {
+  void singletonTrivialRecordArray() {
     var data = new byte[] {0x00};
 
     record TestRecord() {}
@@ -408,7 +417,7 @@
   }
 
   @Test
-  void testEmptyTrivialRecordArray() {
+  void emptyTrivialRecordArray() {
     var data = new byte[] {};
 
     record TestRecord() {}
@@ -418,7 +427,7 @@
   }
 
   @Test
-  void testVariantArray() {
+  void variantArray() {
     var data = new byte[] {};
 
     record TestRecord() {}
@@ -428,7 +437,7 @@
   }
 
   @Test
-  void testInvalidVariantSignature() {
+  void invalidVariantSignature() {
     var data = new byte[] {0x00, 0x00, 0x2E};
 
     var decoder = Decoder.ofVariant();
@@ -436,7 +445,7 @@
   }
 
   @Test
-  void testMissingVariantSignature() {
+  void missingVariantSignature() {
     var data = new byte[] {0x01};
 
     var decoder = Decoder.ofVariant();
@@ -444,7 +453,7 @@
   }
 
   @Test
-  void testSimpleVariantRecord() throws ParseException {
+  void simpleVariantRecord() throws ParseException {
     // signature: "(bynqiuxtdsogvmiai)"
     var data =
         new byte[] {
@@ -494,7 +503,7 @@
   }
 
   @Test
-  void testSignatureString() throws ParseException {
+  void signatureString() throws ParseException {
     var data =
         new byte[] {
           0x28, 0x62, 0x79, 0x6E, 0x71, 0x69, 0x75, 0x78, 0x74, 0x64, 0x73, 0x6F, 0x67, 0x76, 0x6D,
@@ -506,21 +515,21 @@
   }
 
   @Test
-  void testMap() {
+  void map() {
     var data = new byte[] {0x0A, 0x0B, 0x0C};
     var decoder = Decoder.ofByteArray().map(bytes -> bytes.length);
     assertEquals(3, decoder.decode(ByteBuffer.wrap(data)));
   }
 
   @Test
-  void testContramap() {
+  void contramap() {
     var data = new byte[] {0x0A, 0x0B, 0x0C};
     var decoder = Decoder.ofByteArray().contramap(bytes -> bytes.slice(1, 1));
     assertArrayEquals(new byte[] {0x0B}, decoder.decode(ByteBuffer.wrap(data)));
   }
 
   @Test
-  void testPredicateTrue() {
+  void predicateTrue() {
     var data = new byte[] {0x00, 0x01, 0x00};
     var innerDecoder = Decoder.ofShort().contramap(bytes -> bytes.slice(1, 2).order(bytes.order()));
     var decoder =
@@ -532,7 +541,7 @@
   }
 
   @Test
-  void testPredicateFalse() {
+  void predicateFalse() {
     var data = new byte[] {0x01, 0x01, 0x00};
     var innerDecoder = Decoder.ofShort().contramap(bytes -> bytes.slice(1, 2).order(bytes.order()));
     var decoder =
@@ -544,7 +553,7 @@
   }
 
   @Test
-  void testByteOrder() {
+  void byteOrder() {
     var data =
         new byte[] {
           0x01, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x04, 0x05, 0x00, 0x00, 0x06, 0x00, 0x07, 0x08,
diff --git a/jgvariant-ostree/pom.xml b/jgvariant-ostree/pom.xml
index 5c01184..79f5f48 100644
--- a/jgvariant-ostree/pom.xml
+++ b/jgvariant-ostree/pom.xml
@@ -63,20 +63,6 @@
       <artifactId>xz</artifactId>
     </dependency>
 
-    <!-- Static analysis -->
-    <dependency>
-      <groupId>org.checkerframework</groupId>
-      <artifactId>checker</artifactId>
-      <scope>provided</scope>
-      <optional>true</optional>
-    </dependency>
-    <dependency>
-      <groupId>org.checkerframework</groupId>
-      <artifactId>checker-qual</artifactId>
-      <scope>provided</scope>
-      <optional>true</optional>
-    </dependency>
-
     <!-- Testing -->
     <dependency>
       <groupId>org.junit.jupiter</groupId>
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Commit.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Commit.java
index 209827d..f14e758 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Commit.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Commit.java
@@ -4,9 +4,10 @@
 
 package eu.mulk.jgvariant.ostree;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import eu.mulk.jgvariant.core.Decoder;
 import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
 import java.util.List;
 
 /**
@@ -48,8 +49,7 @@
   public record RelatedObject(String ref, Checksum commitChecksum) {
 
     private static final Decoder<RelatedObject> DECODER =
-        Decoder.ofStructure(
-            RelatedObject.class, Decoder.ofString(StandardCharsets.UTF_8), Checksum.decoder());
+        Decoder.ofStructure(RelatedObject.class, Decoder.ofString(UTF_8), Checksum.decoder());
 
     public static Decoder<RelatedObject> decoder() {
       return DECODER;
@@ -62,8 +62,8 @@
           Metadata.decoder(),
           Checksum.decoder(),
           Decoder.ofArray(RelatedObject.decoder()),
-          Decoder.ofString(StandardCharsets.UTF_8),
-          Decoder.ofString(StandardCharsets.UTF_8),
+          Decoder.ofString(UTF_8),
+          Decoder.ofString(UTF_8),
           Decoder.ofLong().withByteOrder(ByteOrder.BIG_ENDIAN),
           Checksum.decoder(),
           Checksum.decoder());
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DirTree.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DirTree.java
index dc38b1b..8ac9e67 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DirTree.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DirTree.java
@@ -4,8 +4,9 @@
 
 package eu.mulk.jgvariant.ostree;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import eu.mulk.jgvariant.core.Decoder;
-import java.nio.charset.StandardCharsets;
 import java.util.List;
 
 /**
@@ -34,8 +35,7 @@
   public record File(String name, Checksum checksum) {
 
     private static final Decoder<File> DECODER =
-        Decoder.ofStructure(
-            File.class, Decoder.ofString(StandardCharsets.UTF_8), Checksum.decoder());
+        Decoder.ofStructure(File.class, Decoder.ofString(UTF_8), Checksum.decoder());
 
     /**
      * Acquires a {@link Decoder} for the enclosing type.
@@ -58,10 +58,7 @@
 
     private static final Decoder<Directory> DECODER =
         Decoder.ofStructure(
-            Directory.class,
-            Decoder.ofString(StandardCharsets.UTF_8),
-            Checksum.decoder(),
-            Checksum.decoder());
+            Directory.class, Decoder.ofString(UTF_8), Checksum.decoder(), Checksum.decoder());
 
     /**
      * Acquires a {@link Decoder} for the enclosing type.
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Metadata.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Metadata.java
index 6e1f820..62f0331 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Metadata.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Metadata.java
@@ -4,9 +4,10 @@
 
 package eu.mulk.jgvariant.ostree;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import eu.mulk.jgvariant.core.Decoder;
 import eu.mulk.jgvariant.core.Variant;
-import java.nio.charset.StandardCharsets;
 import java.util.Map;
 
 /**
@@ -19,8 +20,7 @@
 public record Metadata(Map<String, Variant> fields) {
 
   private static final Decoder<Metadata> DECODER =
-      Decoder.ofDictionary(Decoder.ofString(StandardCharsets.UTF_8), Decoder.ofVariant())
-          .map(Metadata::new);
+      Decoder.ofDictionary(Decoder.ofString(UTF_8), Decoder.ofVariant()).map(Metadata::new);
 
   /**
    * Acquires a {@link Decoder} for the enclosing type.
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SignedDelta.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SignedDelta.java
index e1fcd53..827d5e4 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SignedDelta.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SignedDelta.java
@@ -4,11 +4,12 @@
 
 package eu.mulk.jgvariant.ostree;
 
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
 import eu.mulk.jgvariant.core.Decoder;
 import eu.mulk.jgvariant.core.Variant;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
 import java.util.Map;
 
 /**
@@ -31,7 +32,7 @@
           SignedDelta.class,
           Decoder.ofLong().withByteOrder(ByteOrder.BIG_ENDIAN),
           ByteString.decoder().map(SignedDelta::decodeSuperblock),
-          Decoder.ofDictionary(Decoder.ofString(StandardCharsets.US_ASCII), Decoder.ofVariant()));
+          Decoder.ofDictionary(Decoder.ofString(US_ASCII), Decoder.ofVariant()));
 
   private static DeltaSuperblock decodeSuperblock(ByteString byteString) {
     return DeltaSuperblock.decoder().decode(ByteBuffer.wrap(byteString.bytes()));
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Summary.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Summary.java
index 150ab00..2b8096d 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Summary.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/Summary.java
@@ -4,9 +4,10 @@
 
 package eu.mulk.jgvariant.ostree;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import eu.mulk.jgvariant.core.Decoder;
 import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
 import java.util.List;
 
 /**
@@ -58,7 +59,7 @@
     }
 
     private static final Decoder<Entry> DECODER =
-        Decoder.ofStructure(Entry.class, Decoder.ofString(StandardCharsets.UTF_8), Value.decoder());
+        Decoder.ofStructure(Entry.class, Decoder.ofString(UTF_8), Value.decoder());
 
     /**
      * Acquires a {@link Decoder} for the enclosing type.
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SummarySignature.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SummarySignature.java
index c878955..3c88759 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SummarySignature.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/SummarySignature.java
@@ -4,9 +4,10 @@
 
 package eu.mulk.jgvariant.ostree;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import eu.mulk.jgvariant.core.Decoder;
 import eu.mulk.jgvariant.core.Variant;
-import java.nio.charset.StandardCharsets;
 import java.util.Map;
 
 /**
@@ -21,8 +22,7 @@
 public record SummarySignature(Map<String, Variant> signatures) {
 
   private static final Decoder<SummarySignature> DECODER =
-      Decoder.ofDictionary(Decoder.ofString(StandardCharsets.UTF_8), Decoder.ofVariant())
-          .map(SummarySignature::new);
+      Decoder.ofDictionary(Decoder.ofString(UTF_8), Decoder.ofVariant()).map(SummarySignature::new);
 
   /**
    * Acquires a {@link Decoder} for the enclosing type.
diff --git a/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/ByteStringTest.java b/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/ByteStringTest.java
index 1d3d84d..cd21e3c 100644
--- a/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/ByteStringTest.java
+++ b/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/ByteStringTest.java
@@ -11,25 +11,25 @@
 class ByteStringTest {
 
   @Test
-  void testToModifiedBase64() {
+  void toModifiedBase64() {
     assertEquals("MciDXVydLGaHpQCRyFFC0bLYU_9Bap+4G07jB1RRDVI", testByteString1.modifiedBase64());
   }
 
   @Test
-  void testOfModifiedBase64() {
+  void ofModifiedBase64() {
     assertEquals(
         testByteString1,
         ByteString.ofModifiedBase64("MciDXVydLGaHpQCRyFFC0bLYU_9Bap+4G07jB1RRDVI"));
   }
 
   @Test
-  void testToHex() {
+  void toHex() {
     assertEquals(
         "31c8835d5c9d2c6687a50091c85142d1b2d853ff416a9fb81b4ee30754510d52", testByteString1.hex());
   }
 
   @Test
-  void testOfHex() {
+  void ofHex() {
     assertEquals(
         testByteString1,
         ByteString.ofHex("31c8835d5c9d2c6687a50091c85142d1b2d853ff416a9fb81b4ee30754510d52"));
diff --git a/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java b/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java
index d8ad271..bca8142 100644
--- a/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java
+++ b/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java
@@ -17,7 +17,12 @@
 import org.junit.jupiter.api.Test;
 
 @TestWithResources
-@SuppressWarnings("initialization.field.uninitialized")
+@SuppressWarnings({
+  "ImmutableListOf1",
+  "ImmutableMapOf1",
+  "initialization.field.uninitialized",
+  "NullAway"
+})
 class OstreeDecoderTest {
 
   @GivenBinaryResource("/ostree/summary")
@@ -42,7 +47,7 @@
   byte[] deltaPartPayloadBytes;
 
   @Test
-  void testSummaryDecoder() {
+  void summaryDecoder() {
     var decoder = Summary.decoder();
     var summary = decoder.decode(ByteBuffer.wrap(summaryBytes));
     assertAll(
@@ -65,35 +70,35 @@
   }
 
   @Test
-  void testCommitDecoder() {
+  void commitDecoder() {
     var decoder = Commit.decoder();
     var commit = decoder.decode(ByteBuffer.wrap(commitBytes));
     System.out.println(commit);
   }
 
   @Test
-  void testDirTreeDecoder() {
+  void dirTreeDecoder() {
     var decoder = DirTree.decoder();
     var dirTree = decoder.decode(ByteBuffer.wrap(dirTreeBytes));
     System.out.println(dirTree);
   }
 
   @Test
-  void testDirMetaDecoder() {
+  void dirMetaDecoder() {
     var decoder = DirMeta.decoder();
     var dirMeta = decoder.decode(ByteBuffer.wrap(dirMetaBytes));
     System.out.println(dirMeta);
   }
 
   @Test
-  void testSuperblockDecoder() {
+  void superblockDecoder() {
     var decoder = DeltaSuperblock.decoder();
     var deltaSuperblock = decoder.decode(ByteBuffer.wrap(deltaSuperblockBytes));
     System.out.println(deltaSuperblock);
   }
 
   @Test
-  void testPartPayloadDecoder() {
+  void partPayloadDecoder() {
     var superblockDecoder = DeltaSuperblock.decoder();
     var superblock = superblockDecoder.decode(ByteBuffer.wrap(deltaSuperblockBytes));
 
diff --git a/jgvariant-parent/pom.xml b/jgvariant-parent/pom.xml
index 5c3f27b..41b2c59 100644
--- a/jgvariant-parent/pom.xml
+++ b/jgvariant-parent/pom.xml
@@ -70,12 +70,13 @@
 
     <apiguardian.version>1.1.2</apiguardian.version>
     <errorprone.version>2.15.0</errorprone.version>
+    <error-prone-support.version>0.4.0</error-prone-support.version>
     <google-java-format.version>1.15.0</google-java-format.version>
     <inject-resources.version>0.3.2</inject-resources.version>
     <jetbrains-annotations.version>23.0.0</jetbrains-annotations.version>
     <junit-jupiter.version>5.9.0</junit-jupiter.version>
+    <nullaway.version>0.10.2</nullaway.version>
     <xz.version>1.9</xz.version>
-    <checker-framework.version>3.25.0</checker-framework.version>
   </properties>
 
   <distributionManagement>
@@ -104,18 +105,6 @@
         <version>${apiguardian.version}</version>
       </dependency>
 
-      <!-- Static analysis -->
-      <dependency>
-        <groupId>org.checkerframework</groupId>
-        <artifactId>checker</artifactId>
-        <version>${checker-framework.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>org.checkerframework</groupId>
-        <artifactId>checker-qual</artifactId>
-        <version>${checker-framework.version}</version>
-      </dependency>
-
       <!-- OSTree compression support -->
       <dependency>
         <groupId>org.tukaani</groupId>
@@ -178,9 +167,10 @@
           <version>${compiler-plugin.version}</version>
           <configuration>
             <fork>true</fork>
+            <showWarnings>true</showWarnings>
             <compilerArgs>
               <arg>-XDcompilePolicy=simple</arg>
-              <arg>-Xplugin:ErrorProne -Xep:InvalidParam:OFF</arg>
+              <arg>-Xplugin:ErrorProne -Xep:InvalidParam:OFF -Xep:CollectorMutability:OFF -Xep:LexicographicalAnnotationListing:OFF -XepOpt:NullAway:AnnotatedPackages=eu.mulk</arg>
               <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
               <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
               <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
@@ -200,14 +190,21 @@
                 <version>${errorprone.version}</version>
               </path>
               <path>
-                <groupId>org.checkerframework</groupId>
-                <artifactId>checker</artifactId>
-                <version>${checker-framework.version}</version>
+                <groupId>tech.picnic.error-prone-support</groupId>
+                <artifactId>error-prone-contrib</artifactId>
+                <version>${error-prone-support.version}</version>
+              </path>
+              <path>
+                <groupId>tech.picnic.error-prone-support</groupId>
+                <artifactId>refaster-runner</artifactId>
+                <version>${error-prone-support.version}</version>
+              </path>
+              <path>
+                <groupId>com.uber.nullaway</groupId>
+                <artifactId>nullaway</artifactId>
+                <version>${nullaway.version}</version>
               </path>
             </annotationProcessorPaths>
-            <annotationProcessors>
-              <annotationProcessor>org.checkerframework.checker.nullness.NullnessChecker</annotationProcessor>
-            </annotationProcessors>
           </configuration>
         </plugin>