diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 17da65591..7395a18ed 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -23,7 +23,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        java_version: ['17', '21']
+        java_version: ['17', '21', '23']
         include:
           - java_version: '17'
             release_build: 'R'
diff --git a/avro/pom.xml b/avro/pom.xml
index 3fdb555cb..f6026badb 100644
--- a/avro/pom.xml
+++ b/avro/pom.xml
@@ -86,13 +86,6 @@ abstractions.
           
         
       
-      
-      
-        org.moditect
-        moditect-maven-plugin
-      
     
   
 
diff --git a/avro/src/moditect/module-info.java b/avro/src/main/java/module-info.java
similarity index 77%
rename from avro/src/moditect/module-info.java
rename to avro/src/main/java/module-info.java
index 94fbb0d19..5b945f86a 100644
--- a/avro/src/moditect/module-info.java
+++ b/avro/src/main/java/module-info.java
@@ -1,12 +1,11 @@
-module tools.jackson.dataformat.avro {
+// Avro Main artifact Module descriptor
+module tools.jackson.dataformat.avro
+{
     requires transitive com.fasterxml.jackson.annotation;
     requires tools.jackson.core;
     requires tools.jackson.databind;
 
-    // silly avro Apache impl, its deps:
-    requires static avro;
-    requires static jackson.core.asl;
-    requires static jackson.mapper.asl;
+    requires org.apache.avro;
 
     exports tools.jackson.dataformat.avro;
     exports tools.jackson.dataformat.avro.annotation;
diff --git a/avro/src/test/java/module-info.java b/avro/src/test/java/module-info.java
new file mode 100644
index 000000000..de2933d75
--- /dev/null
+++ b/avro/src/test/java/module-info.java
@@ -0,0 +1,35 @@
+// Avro unit test Module descriptor
+module tools.jackson.dataformat.avro
+{
+    // Since we are not split from Main artifact, will not
+    // need to depend on Main artifact -- but need its dependencies
+    
+    requires tools.jackson.core;
+    requires tools.jackson.databind;
+
+    requires org.apache.avro;
+
+    // Additional test lib/framework dependencies
+    requires org.assertj.core;
+    requires org.junit.jupiter.api;
+    requires org.junit.jupiter.params;
+
+    // Further, need to open up some packages for JUnit et al
+
+    opens tools.jackson.dataformat.avro;
+    opens tools.jackson.dataformat.avro.annotation;
+    opens tools.jackson.dataformat.avro.dos;
+    opens tools.jackson.dataformat.avro.fuzz;
+    opens tools.jackson.dataformat.avro.gen;
+    opens tools.jackson.dataformat.avro.interop;
+    opens tools.jackson.dataformat.avro.interop.annotations;
+    opens tools.jackson.dataformat.avro.interop.arrays;
+    opens tools.jackson.dataformat.avro.interop.maps;
+    opens tools.jackson.dataformat.avro.interop.records;
+    opens tools.jackson.dataformat.avro.jsr310;
+    opens tools.jackson.dataformat.avro.schema;
+    opens tools.jackson.dataformat.avro.schemaev;
+    opens tools.jackson.dataformat.avro.testsupport;
+    opens tools.jackson.dataformat.avro.testutil.failure;
+    opens tools.jackson.dataformat.avro.tofix;
+}
diff --git a/avro/src/test/java/tools/jackson/dataformat/avro/BigDecimal_schemaCreationTest.java b/avro/src/test/java/tools/jackson/dataformat/avro/BigDecimal_schemaCreationTest.java
index 6c5b8ce3c..037e9c48d 100644
--- a/avro/src/test/java/tools/jackson/dataformat/avro/BigDecimal_schemaCreationTest.java
+++ b/avro/src/test/java/tools/jackson/dataformat/avro/BigDecimal_schemaCreationTest.java
@@ -42,7 +42,7 @@ public void testSchemaCreation_withLogicalTypesDisabled_onBigDecimalWithAvroDeci
         // because logical types are disabled by default.
         final Schema actualSchema = gen.getGeneratedSchema().getAvroSchema();
 
-        System.out.println(BigDecimalWithAvroDecimalAnnotationWrapper.class.getSimpleName() + " schema:" + actualSchema.toString(true));
+        //System.out.println(BigDecimalWithAvroDecimalAnnotationWrapper.class.getSimpleName() + " schema:" + actualSchema.toString(true));
 
         // THEN
         assertThat(actualSchema.getField("bigDecimalValue")).isNotNull();
@@ -64,7 +64,7 @@ public void testSchemaCreation_withLogicalTypesEnabled_onBigDecimalWithAvroDecim
         MAPPER.acceptJsonFormatVisitor(BigDecimalWithAvroDecimalAnnotationWrapper.class, gen);
         final Schema actualSchema = gen.getGeneratedSchema().getAvroSchema();
 
-        System.out.println(BigDecimalWithAvroDecimalAnnotationWrapper.class.getSimpleName() + " schema:" + actualSchema.toString(true));
+        //System.out.println(BigDecimalWithAvroDecimalAnnotationWrapper.class.getSimpleName() + " schema:" + actualSchema.toString(true));
 
         // THEN
         assertThat(actualSchema.getField("bigDecimalValue")).isNotNull();
@@ -97,7 +97,7 @@ public void testSchemaCreation_withLogicalTypesEnabled_onBigDecimalWithAvroDecim
         MAPPER.acceptJsonFormatVisitor(BigDecimalWithAvroDecimalAnnotationToFixedWrapper.class, gen);
         final Schema actualSchema = gen.getGeneratedSchema().getAvroSchema();
 
-        System.out.println(BigDecimalWithAvroDecimalAnnotationToFixedWrapper.class.getSimpleName() + " schema:" + actualSchema.toString(true));
+        //System.out.println(BigDecimalWithAvroDecimalAnnotationToFixedWrapper.class.getSimpleName() + " schema:" + actualSchema.toString(true));
 
         // THEN
         assertThat(actualSchema.getField("bigDecimalValue")).isNotNull();
diff --git a/cbor/pom.xml b/cbor/pom.xml
index f84821ada..f0f93aca8 100644
--- a/cbor/pom.xml
+++ b/cbor/pom.xml
@@ -64,13 +64,6 @@ encoded data using Jackson abstractions (streaming API, data binding, tree model
           
         
       
-      
-      
-	org.moditect
-	moditect-maven-plugin
-      
     
   
 
diff --git a/cbor/src/moditect/module-info.java b/cbor/src/main/java/module-info.java
similarity index 83%
rename from cbor/src/moditect/module-info.java
rename to cbor/src/main/java/module-info.java
index 9727e05a2..1c27f7791 100644
--- a/cbor/src/moditect/module-info.java
+++ b/cbor/src/main/java/module-info.java
@@ -1,4 +1,6 @@
-module tools.jackson.dataformat.cbor {
+// CBOR Main artifact Module descriptor
+module tools.jackson.dataformat.cbor
+{
     requires tools.jackson.core;
     requires tools.jackson.databind;
 
diff --git a/cbor/src/test/java/module-info.java b/cbor/src/test/java/module-info.java
new file mode 100644
index 000000000..82812820b
--- /dev/null
+++ b/cbor/src/test/java/module-info.java
@@ -0,0 +1,27 @@
+// CBOR unit test Module descriptor
+module tools.jackson.dataformat.cbor
+{
+    // Since we are not split from Main artifact, will not
+    // need to depend on Main artifact -- but need its dependencies
+    
+    requires tools.jackson.core;
+    requires tools.jackson.databind;
+
+    // Additional test lib/framework dependencies
+    requires org.junit.jupiter.api;
+    requires org.junit.jupiter.params;
+
+    // Further, need to open up some packages for JUnit et al
+    opens tools.jackson.dataformat.cbor;
+    opens tools.jackson.dataformat.cbor.constraints;
+    opens tools.jackson.dataformat.cbor.dos;
+    opens tools.jackson.dataformat.cbor.filter;
+    opens tools.jackson.dataformat.cbor.fuzz;
+    opens tools.jackson.dataformat.cbor.gen;
+    opens tools.jackson.dataformat.cbor.gen.dos;
+    opens tools.jackson.dataformat.cbor.mapper;
+    opens tools.jackson.dataformat.cbor.parse;
+    opens tools.jackson.dataformat.cbor.seq;
+    opens tools.jackson.dataformat.cbor.testutil;
+    opens tools.jackson.dataformat.cbor.testutil.failure;
+}
diff --git a/cbor/src/test/java/tools/jackson/dataformat/cbor/parse/SymbolTableTest.java b/cbor/src/test/java/tools/jackson/dataformat/cbor/parse/SymbolTableTest.java
index 3b1355e0d..f4596e406 100644
--- a/cbor/src/test/java/tools/jackson/dataformat/cbor/parse/SymbolTableTest.java
+++ b/cbor/src/test/java/tools/jackson/dataformat/cbor/parse/SymbolTableTest.java
@@ -36,19 +36,19 @@ public void testSimpleDefault() throws Exception
             assertTrue(syms.isCanonicalizing()); // added in 2.13
 
             assertEquals(0, syms.size());
-            assertEquals(0, _findParent(syms).size());
+            //assertEquals(0, _findParent(syms).size());
 
             assertToken(JsonToken.START_OBJECT, p.nextToken());
             assertToken(JsonToken.PROPERTY_NAME, p.nextToken());
             assertEquals("a", p.currentName());
             assertEquals(1, syms.size());
             // not yet synced to parent
-            assertEquals(0, _findParent(syms).size());
+            //assertEquals(0, _findParent(syms).size());
 
             while (p.nextToken() != null) { ; }
             assertEquals(2, syms.size());
             // but after closing, should sync
-            assertEquals(2, _findParent(syms).size());
+            //assertEquals(2, _findParent(syms).size());
         }
 
         // by default, should canonicalize etc:
@@ -56,7 +56,7 @@ public void testSimpleDefault() throws Exception
             ByteQuadsCanonicalizer syms = _findSymbols(p);
             assertEquals(2, syms.size());
             // also check that parent (root) has it all?
-            assertEquals(2, _findParent(syms).size());
+            //assertEquals(2, _findParent(syms).size());
 
             // but no additions second time around
             while (p.nextToken() != null) { ; }
@@ -118,7 +118,7 @@ public void testSimpleNoCanonicalize() throws Exception
             assertFalse(syms.isCanonicalizing()); // added in 2.13
             assertEquals(-1, syms.size());
             // also, should not have parent:
-            assertNull(_findParent(syms));
+            //assertNull(_findParent(syms));
 
             assertToken(JsonToken.START_OBJECT, p.nextToken());
             assertToken(JsonToken.PROPERTY_NAME, p.nextToken());
@@ -227,10 +227,13 @@ private ByteQuadsCanonicalizer _findSymbols(JsonParser p) throws Exception
         return (ByteQuadsCanonicalizer) f.get(p);
     }
 
+    // Cannot access under JPMS, alas
+    /*
     private ByteQuadsCanonicalizer _findParent(ByteQuadsCanonicalizer sym) throws Exception
     {
         Field f = ByteQuadsCanonicalizer.class.getDeclaredField("_parent");
         f.setAccessible(true);
         return (ByteQuadsCanonicalizer) f.get(sym);
     }
+    */
 }
diff --git a/ion/pom.xml b/ion/pom.xml
index 6277f492b..8ea7cbf40 100644
--- a/ion/pom.xml
+++ b/ion/pom.xml
@@ -78,13 +78,6 @@ tree model)
           
         
       
-      
-      
-	org.moditect
-	moditect-maven-plugin
-      
     
   
 
diff --git a/ion/src/moditect/module-info.java b/ion/src/main/java/module-info.java
similarity index 66%
rename from ion/src/moditect/module-info.java
rename to ion/src/main/java/module-info.java
index 1ed9f7768..02e58bec3 100644
--- a/ion/src/moditect/module-info.java
+++ b/ion/src/main/java/module-info.java
@@ -1,17 +1,17 @@
-// Generated 15-Mar-2019 using Moditect maven plugin
-module tools.jackson.dataformat.ion {
-    requires transitive com.fasterxml.jackson.annotation;
+// Ion Main artifact Module descriptor
+module tools.jackson.dataformat.ion
+{
     requires tools.jackson.core;
     requires tools.jackson.databind;
-
-    requires static ion.java;
-
     requires java.sql;
 
+    // ion-java has no explicit module-info; but automatic name is:
+    requires com.amazon.ion;
+
     exports tools.jackson.dataformat.ion;
     exports tools.jackson.dataformat.ion.ionvalue;
+    exports tools.jackson.dataformat.ion.jsr310;
     exports tools.jackson.dataformat.ion.polymorphism;
-    exports tools.jackson.dataformat.ion.util;
 
     provides tools.jackson.core.TokenStreamFactory with
         tools.jackson.dataformat.ion.IonFactory;
diff --git a/ion/src/test/java/module-info.java b/ion/src/test/java/module-info.java
new file mode 100644
index 000000000..cce19b456
--- /dev/null
+++ b/ion/src/test/java/module-info.java
@@ -0,0 +1,28 @@
+// Ion unit test Module descriptor
+module tools.jackson.dataformat.ion
+{
+    // Since we are not split from Main artifact, will not
+    // need to depend on Main artifact -- but need its dependencies
+    
+    requires tools.jackson.core;
+    requires tools.jackson.databind;
+    requires java.sql;
+
+    requires com.amazon.ion;
+    
+    // Additional test lib/framework dependencies
+    requires org.junit.jupiter.api;
+    requires org.junit.jupiter.params;
+
+    // Further, need to open up some packages for JUnit et al
+    opens tools.jackson.dataformat.ion;
+    opens tools.jackson.dataformat.ion.dos;
+    opens tools.jackson.dataformat.ion.fuzz;
+    opens tools.jackson.dataformat.ion.ionvalue;
+    opens tools.jackson.dataformat.ion.jsr310;
+    opens tools.jackson.dataformat.ion.misc;
+    opens tools.jackson.dataformat.ion.polymorphism;
+    opens tools.jackson.dataformat.ion.sequence;
+    opens tools.jackson.dataformat.ion.testutil.failure;
+    opens tools.jackson.dataformat.ion.tofix;
+}
diff --git a/protobuf/pom.xml b/protobuf/pom.xml
index a9c1e86ae..ba8063bdb 100644
--- a/protobuf/pom.xml
+++ b/protobuf/pom.xml
@@ -50,13 +50,6 @@ abstractions.
       tools.jackson.core
       jackson-databind
     
-
-    
-    
-      com.fasterxml.jackson.core
-      jackson-annotations
-      provided
-    
   
 
   
@@ -73,43 +66,6 @@ abstractions.
         
      
 
-      
-        
-        org.apache.maven.plugins
-        maven-shade-plugin
-        
-          
-            package
-            
-              shade
-            
-            
-              
-                
-                  
-                          
-                  null:null
-                
-              
-              
-                
-                  com.squareup
-                  tools.jackson.dataformat.protobuf.protoparser
-                
-              
-            
-          
-        
-      
-            
-      
-	org.moditect
-	moditect-maven-plugin
-      
    
   
 
diff --git a/protobuf/src/main/java/module-info.java b/protobuf/src/main/java/module-info.java
new file mode 100644
index 000000000..11a8fb60c
--- /dev/null
+++ b/protobuf/src/main/java/module-info.java
@@ -0,0 +1,21 @@
+// Protobuf Main artifact Module descriptor
+module tools.jackson.dataformat.protobuf
+{
+    requires transitive tools.jackson.core;
+    requires transitive tools.jackson.databind;
+
+    // No module-info nor Automatic-Module-Name; relies on jar name:
+    requires protoparser;
+
+    exports tools.jackson.dataformat.protobuf;
+    exports tools.jackson.dataformat.protobuf.schema;
+    exports tools.jackson.dataformat.protobuf.schemagen;
+
+    // Need to "opens" to allow reading resource `descriptor.proto`
+    opens tools.jackson.dataformat.protobuf.schema;
+
+    provides tools.jackson.core.TokenStreamFactory with
+        tools.jackson.dataformat.protobuf.ProtobufFactory;
+    provides tools.jackson.databind.ObjectMapper with
+        tools.jackson.dataformat.protobuf.ProtobufMapper;
+}
diff --git a/protobuf/src/main/java/tools/jackson/dataformat/protobuf/ProtobufMapper.java b/protobuf/src/main/java/tools/jackson/dataformat/protobuf/ProtobufMapper.java
index 0df7ce6bc..5b1c79894 100644
--- a/protobuf/src/main/java/tools/jackson/dataformat/protobuf/ProtobufMapper.java
+++ b/protobuf/src/main/java/tools/jackson/dataformat/protobuf/ProtobufMapper.java
@@ -4,6 +4,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.util.Objects;
 import java.util.concurrent.locks.ReentrantLock;
 
 import tools.jackson.core.Version;
@@ -34,6 +35,13 @@ public Builder(ProtobufFactory f) {
             super(f);
         }
 
+        /**
+         * NOTE: while technically public, not intended for external use
+         * (since {@code StateImpl} is not public type)
+         *
+         * @param state State to restore to initialize constructed Builder
+         */
+        @SuppressWarnings("exports")
         public Builder(StateImpl state) {
             super(state);
         }
@@ -209,15 +217,15 @@ public ProtobufSchema generateSchemaFor(TypeReference> type) {
      */
 
     public FileDescriptorSet loadDescriptorSet(URL src) throws IOException {
-        return descriptorLoader().load(src);
+        return descriptorLoader().load(Objects.requireNonNull(src));
     }
 
     public FileDescriptorSet loadDescriptorSet(File src) throws IOException {
-        return descriptorLoader().load(src);
+        return descriptorLoader().load(Objects.requireNonNull(src));
     }
 
     public FileDescriptorSet loadDescriptorSet(InputStream src) throws IOException {
-        return descriptorLoader().load(src);
+        return descriptorLoader().load(Objects.requireNonNull(src));
     }
 
     /**
diff --git a/protobuf/src/main/java/tools/jackson/dataformat/protobuf/schema/DescriptorLoader.java b/protobuf/src/main/java/tools/jackson/dataformat/protobuf/schema/DescriptorLoader.java
index 39a436d05..8ebe85488 100644
--- a/protobuf/src/main/java/tools/jackson/dataformat/protobuf/schema/DescriptorLoader.java
+++ b/protobuf/src/main/java/tools/jackson/dataformat/protobuf/schema/DescriptorLoader.java
@@ -2,6 +2,7 @@
 
 import java.io.*;
 import java.net.URL;
+import java.util.Objects;
 
 import tools.jackson.databind.ObjectMapper;
 import tools.jackson.databind.ObjectReader;
@@ -15,7 +16,8 @@
  */
 public class DescriptorLoader
 {
-    protected final static String DESCRIPTOR_PROTO = "/descriptor.proto";
+    protected final static String DESCRIPTOR_PROTO = "/tools/jackson/dataformat/protobuf/schema/descriptor.proto";
+    //protected final static String DESCRIPTOR_PROTO = "descriptor.proto";
 
     /**
      * Fully configured reader for {@link FileDescriptorSet} objects.
@@ -38,13 +40,20 @@ public static DescriptorLoader construct(ProtobufMapper mapper) throws IOExcepti
     }
 
     /**
-     * @param mapper {@link ObjectMapper} that can reader protoc content.
+     * @param mapper {@link ObjectMapper} that can read protoc content.
      */
     public static DescriptorLoader construct(ObjectMapper mapper,
             ProtobufSchemaLoader schemaLoader) throws IOException
     {
         ProtobufSchema schema;
-        try (InputStream in = DescriptorLoader.class.getResourceAsStream(DESCRIPTOR_PROTO)) {
+        final Class> ctxt = DescriptorLoader.class;
+        final String resourceName = DESCRIPTOR_PROTO;
+        try (InputStream in = ctxt.getResourceAsStream(resourceName)) {
+            if (in == null) {
+                throw new IllegalStateException(String.format(
+                        "Can not find resource `%s` within context '%s'",
+                        resourceName, ctxt.getName()));
+            }
             schema = schemaLoader.load(in, "FileDescriptorSet");
         }
         return new DescriptorLoader(mapper.readerFor(FileDescriptorSet.class)
@@ -59,12 +68,12 @@ public static DescriptorLoader construct(ObjectMapper mapper,
 
     public FileDescriptorSet load(URL src) throws IOException
     {
-        return _reader.readValue(src);
+        return _reader.readValue(Objects.requireNonNull(src));
     }
 
     public FileDescriptorSet load(File src) throws IOException
     {
-        return _reader.readValue(src);
+        return _reader.readValue(Objects.requireNonNull(src));
     }
 
     /**
@@ -72,7 +81,7 @@ public FileDescriptorSet load(File src) throws IOException
      */
     public FileDescriptorSet load(InputStream in) throws IOException
     {
-        return _reader.readValue(in);
+        return _reader.readValue(Objects.requireNonNull(in));
     }
 
     /**
@@ -80,6 +89,6 @@ public FileDescriptorSet load(InputStream in) throws IOException
      */
     public FileDescriptorSet load(Reader r) throws IOException
     {
-        return _reader.readValue(r);
+        return _reader.readValue(Objects.requireNonNull(r));
     }
 }
diff --git a/protobuf/src/main/java/tools/jackson/dataformat/protobuf/schema/ProtobufSchemaLoader.java b/protobuf/src/main/java/tools/jackson/dataformat/protobuf/schema/ProtobufSchemaLoader.java
index 7f2bb1636..bb7f7db4e 100644
--- a/protobuf/src/main/java/tools/jackson/dataformat/protobuf/schema/ProtobufSchemaLoader.java
+++ b/protobuf/src/main/java/tools/jackson/dataformat/protobuf/schema/ProtobufSchemaLoader.java
@@ -3,6 +3,7 @@
 import java.io.*;
 import java.net.URL;
 import java.nio.charset.Charset;
+import java.util.Objects;
 
 import com.squareup.protoparser.ProtoFile;
 import com.squareup.protoparser.ProtoParser;
@@ -40,7 +41,7 @@ public ProtobufSchemaLoader() { }
      */
 
     public ProtobufSchema load(URL url) throws IOException {
-        return loadNative(url).forFirstType();
+        return loadNative(Objects.requireNonNull(url)).forFirstType();
     }
 
     /**
@@ -48,11 +49,11 @@ public ProtobufSchema load(URL url) throws IOException {
      *   the root value to read/write
      */
     public ProtobufSchema load(URL url, String rootTypeName) throws IOException {
-        return loadNative(url).forType(rootTypeName);
+        return loadNative(Objects.requireNonNull(url)).forType(rootTypeName);
     }
 
     public ProtobufSchema load(File f) throws IOException {
-        return loadNative(f).forFirstType();
+        return loadNative(Objects.requireNonNull(f)).forFirstType();
     }
 
     /**
@@ -60,7 +61,7 @@ public ProtobufSchema load(File f) throws IOException {
      *   the root value to read/write
      */
     public ProtobufSchema load(File f, String rootTypeName) throws IOException {
-        return loadNative(f).forType(rootTypeName);
+        return loadNative(Objects.requireNonNull(f)).forType(rootTypeName);
     }
 
     /**
@@ -69,7 +70,7 @@ public ProtobufSchema load(File f, String rootTypeName) throws IOException {
      * Note that given {@link InputStream} will be closed before method returns.
      */
     public ProtobufSchema load(InputStream in) throws IOException {
-        return loadNative(in, true).forFirstType();
+        return loadNative(Objects.requireNonNull(in), true).forFirstType();
     }
 
     /**
@@ -77,7 +78,7 @@ public ProtobufSchema load(InputStream in) throws IOException {
      *   the root value to read/write
      */
     public ProtobufSchema load(InputStream in, String rootTypeName) throws IOException {
-        return loadNative(in, true).forType(rootTypeName);
+        return loadNative(Objects.requireNonNull(in), true).forType(rootTypeName);
     }
 
     /**
@@ -86,7 +87,7 @@ public ProtobufSchema load(InputStream in, String rootTypeName) throws IOExcepti
      * Note that given {@link Reader} will be closed before method returns.
      */
     public ProtobufSchema load(Reader r) throws IOException {
-        return loadNative(r, true).forFirstType();
+        return loadNative(Objects.requireNonNull(r), true).forFirstType();
     }
 
     /**
@@ -94,7 +95,7 @@ public ProtobufSchema load(Reader r) throws IOException {
      *   the root value to read/write
      */
     public ProtobufSchema load(Reader r, String rootTypeName) throws IOException {
-        return loadNative(r, true).forType(rootTypeName);
+        return loadNative(Objects.requireNonNull(r), true).forType(rootTypeName);
     }
 
     /**
@@ -120,22 +121,27 @@ public ProtobufSchema parse(String schemaAsString, String rootTypeName) throws I
      */
 
     public NativeProtobufSchema loadNative(File f) throws IOException {
+        Objects.requireNonNull(f);
         return NativeProtobufSchema.construct(_loadNative(f));
     }
 
     public NativeProtobufSchema loadNative(URL url) throws IOException {
+        Objects.requireNonNull(url);
         return NativeProtobufSchema.construct(_loadNative(url));
     }
 
     public NativeProtobufSchema parseNative(String schema) throws IOException {
+        Objects.requireNonNull(schema);
         return NativeProtobufSchema.construct(_loadNative(schema));
     }
 
     public NativeProtobufSchema loadNative(InputStream in, boolean close) throws IOException {
+        Objects.requireNonNull(in);
         return NativeProtobufSchema.construct(_loadNative(in, close));
     }
 
     protected NativeProtobufSchema loadNative(Reader r, boolean close) throws IOException {
+        Objects.requireNonNull(r);
         return NativeProtobufSchema.construct(_loadNative(r, close));
     }
 
diff --git a/protobuf/src/main/resources/descriptor.proto b/protobuf/src/main/resources/tools/jackson/dataformat/protobuf/schema/descriptor.proto
similarity index 100%
rename from protobuf/src/main/resources/descriptor.proto
rename to protobuf/src/main/resources/tools/jackson/dataformat/protobuf/schema/descriptor.proto
diff --git a/protobuf/src/moditect/module-info.java b/protobuf/src/moditect/module-info.java
deleted file mode 100644
index a1148c5bd..000000000
--- a/protobuf/src/moditect/module-info.java
+++ /dev/null
@@ -1,15 +0,0 @@
-module tools.jackson.dataformat.protobuf {
-    requires tools.jackson.core;
-    requires tools.jackson.databind;
-
-    exports tools.jackson.dataformat.protobuf;
-// No, should not expose shaded
-//    exports tools.jackson.dataformat.protobuf.protoparser.protoparser;
-    exports tools.jackson.dataformat.protobuf.schema;
-    exports tools.jackson.dataformat.protobuf.schemagen;
-
-    provides tools.jackson.core.TokenStreamFactory with
-        tools.jackson.dataformat.protobuf.ProtobufFactory;
-    provides tools.jackson.databind.ObjectMapper with
-        tools.jackson.dataformat.protobuf.ProtobufMapper;
-}
diff --git a/protobuf/src/test/java/module-info.java b/protobuf/src/test/java/module-info.java
new file mode 100644
index 000000000..da0366bdc
--- /dev/null
+++ b/protobuf/src/test/java/module-info.java
@@ -0,0 +1,24 @@
+// Protobuf unit test Module descriptor
+module tools.jackson.dataformat.protobuf
+{
+    // Since we are not split from Main artifact, will not
+    // need to depend on Main artifact -- but need its dependencies
+    
+    requires tools.jackson.core;
+    requires tools.jackson.databind;
+
+    requires protoparser;
+
+    // Additional test lib/framework dependencies
+    requires org.junit.jupiter.api;
+    requires org.junit.jupiter.params;
+
+    // Further, need to open up some packages for JUnit et al
+    opens tools.jackson.dataformat.protobuf;
+    opens tools.jackson.dataformat.protobuf.dos;
+    opens tools.jackson.dataformat.protobuf.fuzz;
+    opens tools.jackson.dataformat.protobuf.schema;
+    opens tools.jackson.dataformat.protobuf.testutil;
+    opens tools.jackson.dataformat.protobuf.testutil.failure;
+    opens tools.jackson.dataformat.protobuf.tofix;
+}
diff --git a/protobuf/src/test/java/tools/jackson/dataformat/protobuf/ProtobufTestBase.java b/protobuf/src/test/java/tools/jackson/dataformat/protobuf/ProtobufTestBase.java
index 761c4328e..15333df0c 100644
--- a/protobuf/src/test/java/tools/jackson/dataformat/protobuf/ProtobufTestBase.java
+++ b/protobuf/src/test/java/tools/jackson/dataformat/protobuf/ProtobufTestBase.java
@@ -318,7 +318,7 @@ public String toString() {
 
     // // // POJOs for "JVM-serializers" case
 
-    protected static class  MediaItem
+    public static class  MediaItem
     {
          public Media media;
          public List images;
@@ -375,9 +375,9 @@ public boolean equals(Object o) {
          }
     }
 
-    enum Size { SMALL, LARGE };
+    public enum Size { SMALL, LARGE };
 
-    static class Image
+    public static class Image
     {
         public Image() { }
         public Image(String uri, String title, int w, int h, Size s) {
@@ -410,9 +410,9 @@ && _equals(size, other.size)
         }
     }
 
-    enum Player { JAVA, FLASH; }
+    public enum Player { JAVA, FLASH; }
 
-    static class Media {
+    public static class Media {
 
         public String uri;
         public String title;        // Can be unset.
diff --git a/protobuf/src/test/java/tools/jackson/dataformat/protobuf/dos/DeepNestingProtobufParserTest.java b/protobuf/src/test/java/tools/jackson/dataformat/protobuf/dos/DeepNestingProtobufParserTest.java
index ab591ef4d..bd7c5ed24 100644
--- a/protobuf/src/test/java/tools/jackson/dataformat/protobuf/dos/DeepNestingProtobufParserTest.java
+++ b/protobuf/src/test/java/tools/jackson/dataformat/protobuf/dos/DeepNestingProtobufParserTest.java
@@ -20,11 +20,11 @@
  */
 public class DeepNestingProtobufParserTest extends ProtobufTestBase
 {
-    static class Node {
+    public static class Node {
         public int id;
         public Node next;
 
-        protected Node() { }
+        public Node() { }
         public Node(int id, Node next) {
             this.id = id;
             this.next = next;
diff --git a/protobuf/src/test/java/tools/jackson/dataformat/protobuf/schema/DescriptorLoaderTest.java b/protobuf/src/test/java/tools/jackson/dataformat/protobuf/schema/DescriptorLoaderTest.java
index 7cd1ba06d..6232be85e 100644
--- a/protobuf/src/test/java/tools/jackson/dataformat/protobuf/schema/DescriptorLoaderTest.java
+++ b/protobuf/src/test/java/tools/jackson/dataformat/protobuf/schema/DescriptorLoaderTest.java
@@ -28,7 +28,6 @@ public Main(Other o)
         }
     }
 
-
     static class Other
     {
         public int f;
@@ -68,7 +67,7 @@ public void testParsing() throws Exception
         // Deserialize the bytes using the descriptor
         // load main.desc descriptor file.  This file was created by protoc - o main.desc main.proto other.proto
         FileDescriptorSet fds;
-        try (InputStream in = this.getClass().getResourceAsStream("/main.desc")) {
+        try (InputStream in = this.getClass().getResourceAsStream("/test/main.desc")) {
             fds = MAPPER.loadDescriptorSet(in);
         }
         ProtobufSchema schema2 = fds.schemaFor("Main");
diff --git a/protobuf/src/test/resources/main.desc b/protobuf/src/test/resources/test/main.desc
similarity index 100%
rename from protobuf/src/test/resources/main.desc
rename to protobuf/src/test/resources/test/main.desc
diff --git a/protobuf/src/test/resources/tools/jackson/dataformat/protobuf/schema/descriptor.proto b/protobuf/src/test/resources/tools/jackson/dataformat/protobuf/schema/descriptor.proto
new file mode 100644
index 000000000..bed737349
--- /dev/null
+++ b/protobuf/src/test/resources/tools/jackson/dataformat/protobuf/schema/descriptor.proto
@@ -0,0 +1,830 @@
+syntax = "proto2";
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: kenton@google.com (Kenton Varda)
+//  Based on original Protocol Buffers design by
+//  Sanjay Ghemawat, Jeff Dean, and others.
+//
+// The messages in this file describe the definitions found in .proto files.
+// A valid .proto file can be translated directly to a FileDescriptorProto
+// without any other information (e.g. without reading its imports).
+
+
+
+package google.protobuf;
+option go_package = "github.com/golang/protobuf/protoc-gen-go/descriptor;descriptor";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "DescriptorProtos";
+option csharp_namespace = "Google.Protobuf.Reflection";
+option objc_class_prefix = "GPB";
+
+// descriptor.proto must be optimized for speed because reflection-based
+// algorithms don't work during bootstrapping.
+option optimize_for = SPEED;
+
+// The protocol compiler can output a FileDescriptorSet containing the .proto
+// files it parses.
+message FileDescriptorSet {
+  repeated FileDescriptorProto file = 1;
+}
+
+// Describes a complete .proto file.
+message FileDescriptorProto {
+  optional string name = 1;       // file name, relative to root of source tree
+  optional string package = 2;    // e.g. "foo", "foo.bar", etc.
+
+  // Names of files imported by this file.
+  repeated string dependency = 3;
+  // Indexes of the public imported files in the dependency list above.
+  repeated int32 public_dependency = 10;
+  // Indexes of the weak imported files in the dependency list.
+  // For Google-internal migration only. Do not use.
+  repeated int32 weak_dependency = 11;
+
+  // All top-level definitions in this file.
+  repeated DescriptorProto message_type = 4;
+  repeated EnumDescriptorProto enum_type = 5;
+  repeated ServiceDescriptorProto service = 6;
+  repeated FieldDescriptorProto extension = 7;
+
+  optional FileOptions options = 8;
+
+  // This field contains optional information about the original source code.
+  // You may safely remove this entire field without harming runtime
+  // functionality of the descriptors -- the information is needed only by
+  // development tools.
+  optional SourceCodeInfo source_code_info = 9;
+
+  // The syntax of the proto file.
+  // The supported values are "proto2" and "proto3".
+  optional string syntax = 12;
+}
+
+// Describes a message type.
+message DescriptorProto {
+  optional string name = 1;
+
+  repeated FieldDescriptorProto field = 2;
+  repeated FieldDescriptorProto extension = 6;
+
+  repeated DescriptorProto nested_type = 3;
+  repeated EnumDescriptorProto enum_type = 4;
+
+  message ExtensionRange {
+    optional int32 start = 1;
+    optional int32 end = 2;
+  }
+  repeated ExtensionRange extension_range = 5;
+
+  repeated OneofDescriptorProto oneof_decl = 8;
+
+  optional MessageOptions options = 7;
+
+  // Range of reserved tag numbers. Reserved tag numbers may not be used by
+  // fields or extension ranges in the same message. Reserved ranges may
+  // not overlap.
+  message ReservedRange {
+    optional int32 start = 1; // Inclusive.
+    optional int32 end = 2;   // Exclusive.
+  }
+  repeated ReservedRange reserved_range = 9;
+  // Reserved field names, which may not be used by fields in the same message.
+  // A given name may only be reserved once.
+  repeated string reserved_name = 10;
+}
+
+// Describes a field within a message.
+message FieldDescriptorProto {
+  enum Type {
+    // 0 is reserved for errors.
+    // Order is weird for historical reasons.
+    TYPE_DOUBLE         = 1;
+    TYPE_FLOAT          = 2;
+    // Not ZigZag encoded.  Negative numbers take 10 bytes.  Use TYPE_SINT64 if
+    // negative values are likely.
+    TYPE_INT64          = 3;
+    TYPE_UINT64         = 4;
+    // Not ZigZag encoded.  Negative numbers take 10 bytes.  Use TYPE_SINT32 if
+    // negative values are likely.
+    TYPE_INT32          = 5;
+    TYPE_FIXED64        = 6;
+    TYPE_FIXED32        = 7;
+    TYPE_BOOL           = 8;
+    TYPE_STRING         = 9;
+    // Tag-delimited aggregate.
+    // Group type is deprecated and not supported in proto3. However, Proto3
+    // implementations should still be able to parse the group wire format and
+    // treat group fields as unknown fields.
+    TYPE_GROUP          = 10;
+    TYPE_MESSAGE        = 11;  // Length-delimited aggregate.
+
+    // New in version 2.
+    TYPE_BYTES          = 12;
+    TYPE_UINT32         = 13;
+    TYPE_ENUM           = 14;
+    TYPE_SFIXED32       = 15;
+    TYPE_SFIXED64       = 16;
+    TYPE_SINT32         = 17;  // Uses ZigZag encoding.
+    TYPE_SINT64         = 18;  // Uses ZigZag encoding.
+  };
+
+  enum Label {
+    // 0 is reserved for errors
+    LABEL_OPTIONAL      = 1;
+    LABEL_REQUIRED      = 2;
+    LABEL_REPEATED      = 3;
+  };
+
+  optional string name = 1;
+  optional int32 number = 3;
+  optional Label label = 4;
+
+  // If type_name is set, this need not be set.  If both this and type_name
+  // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP.
+  optional Type type = 5;
+
+  // For message and enum types, this is the name of the type.  If the name
+  // starts with a '.', it is fully-qualified.  Otherwise, C++-like scoping
+  // rules are used to find the type (i.e. first the nested types within this
+  // message are searched, then within the parent, on up to the root
+  // namespace).
+  optional string type_name = 6;
+
+  // For extensions, this is the name of the type being extended.  It is
+  // resolved in the same manner as type_name.
+  optional string extendee = 2;
+
+  // For numeric types, contains the original text representation of the value.
+  // For booleans, "true" or "false".
+  // For strings, contains the default text contents (not escaped in any way).
+  // For bytes, contains the C escaped value.  All bytes >= 128 are escaped.
+  // TODO(kenton):  Base-64 encode?
+  optional string default_value = 7;
+
+  // If set, gives the index of a oneof in the containing type's oneof_decl
+  // list.  This field is a member of that oneof.
+  optional int32 oneof_index = 9;
+
+  // JSON name of this field. The value is set by protocol compiler. If the
+  // user has set a "json_name" option on this field, that option's value
+  // will be used. Otherwise, it's deduced from the field's name by converting
+  // it to camelCase.
+  optional string json_name = 10;
+
+  optional FieldOptions options = 8;
+}
+
+// Describes a oneof.
+message OneofDescriptorProto {
+  optional string name = 1;
+  optional OneofOptions options = 2;
+}
+
+// Describes an enum type.
+message EnumDescriptorProto {
+  optional string name = 1;
+
+  repeated EnumValueDescriptorProto value = 2;
+
+  optional EnumOptions options = 3;
+}
+
+// Describes a value within an enum.
+message EnumValueDescriptorProto {
+  optional string name = 1;
+  optional int32 number = 2;
+
+  optional EnumValueOptions options = 3;
+}
+
+// Describes a service.
+message ServiceDescriptorProto {
+  optional string name = 1;
+  repeated MethodDescriptorProto method = 2;
+
+  optional ServiceOptions options = 3;
+}
+
+// Describes a method of a service.
+message MethodDescriptorProto {
+  optional string name = 1;
+
+  // Input and output type names.  These are resolved in the same way as
+  // FieldDescriptorProto.type_name, but must refer to a message type.
+  optional string input_type = 2;
+  optional string output_type = 3;
+
+  optional MethodOptions options = 4;
+
+  // Identifies if client streams multiple client messages
+  optional bool client_streaming = 5 [default=false];
+  // Identifies if server streams multiple server messages
+  optional bool server_streaming = 6 [default=false];
+}
+
+
+// ===================================================================
+// Options
+
+// Each of the definitions above may have "options" attached.  These are
+// just annotations which may cause code to be generated slightly differently
+// or may contain hints for code that manipulates protocol messages.
+//
+// Clients may define custom options as extensions of the *Options messages.
+// These extensions may not yet be known at parsing time, so the parser cannot
+// store the values in them.  Instead it stores them in a field in the *Options
+// message called uninterpreted_option. This field must have the same name
+// across all *Options messages. We then use this field to populate the
+// extensions when we build a descriptor, at which point all protos have been
+// parsed and so all extensions are known.
+//
+// Extension numbers for custom options may be chosen as follows:
+// * For options which will only be used within a single application or
+//   organization, or for experimental options, use field numbers 50000
+//   through 99999.  It is up to you to ensure that you do not use the
+//   same number for multiple options.
+// * For options which will be published and used publicly by multiple
+//   independent entities, e-mail protobuf-global-extension-registry@google.com
+//   to reserve extension numbers. Simply provide your project name (e.g.
+//   Objective-C plugin) and your project website (if available) -- there's no
+//   need to explain how you intend to use them. Usually you only need one
+//   extension number. You can declare multiple options with only one extension
+//   number by putting them in a sub-message. See the Custom Options section of
+//   the docs for examples:
+//   https://developers.google.com/protocol-buffers/docs/proto#options
+//   If this turns out to be popular, a web service will be set up
+//   to automatically assign option numbers.
+
+
+message FileOptions {
+
+  // Sets the Java package where classes generated from this .proto will be
+  // placed.  By default, the proto package is used, but this is often
+  // inappropriate because proto packages do not normally start with backwards
+  // domain names.
+  optional string java_package = 1;
+
+
+  // If set, all the classes from the .proto file are wrapped in a single
+  // outer class with the given name.  This applies to both Proto1
+  // (equivalent to the old "--one_java_file" option) and Proto2 (where
+  // a .proto always translates to a single class, but you may want to
+  // explicitly choose the class name).
+  optional string java_outer_classname = 8;
+
+  // If set true, then the Java code generator will generate a separate .java
+  // file for each top-level message, enum, and service defined in the .proto
+  // file.  Thus, these types will *not* be nested inside the outer class
+  // named by java_outer_classname.  However, the outer class will still be
+  // generated to contain the file's getDescriptor() method as well as any
+  // top-level extensions defined in the file.
+  optional bool java_multiple_files = 10 [default=false];
+
+  // This option does nothing.
+  optional bool java_generate_equals_and_hash = 20 [deprecated=true];
+
+  // If set true, then the Java2 code generator will generate code that
+  // throws an exception whenever an attempt is made to assign a non-UTF-8
+  // byte sequence to a string field.
+  // Message reflection will do the same.
+  // However, an extension field still accepts non-UTF-8 byte sequences.
+  // This option has no effect on when used with the lite runtime.
+  optional bool java_string_check_utf8 = 27 [default=false];
+
+
+  // Generated classes can be optimized for speed or code size.
+  enum OptimizeMode {
+    SPEED = 1;        // Generate complete code for parsing, serialization,
+                      // etc.
+    CODE_SIZE = 2;    // Use ReflectionOps to implement these methods.
+    LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime.
+  }
+  optional OptimizeMode optimize_for = 9 [default=SPEED];
+
+  // Sets the Go package where structs generated from this .proto will be
+  // placed. If omitted, the Go package will be derived from the following:
+  //   - The basename of the package import path, if provided.
+  //   - Otherwise, the package statement in the .proto file, if present.
+  //   - Otherwise, the basename of the .proto file, without extension.
+  optional string go_package = 11;
+
+
+
+  // Should generic services be generated in each language?  "Generic" services
+  // are not specific to any particular RPC system.  They are generated by the
+  // main code generators in each language (without additional plugins).
+  // Generic services were the only kind of service generation supported by
+  // early versions of google.protobuf.
+  //
+  // Generic services are now considered deprecated in favor of using plugins
+  // that generate code specific to your particular RPC system.  Therefore,
+  // these default to false.  Old code which depends on generic services should
+  // explicitly set them to true.
+  optional bool cc_generic_services = 16 [default=false];
+  optional bool java_generic_services = 17 [default=false];
+  optional bool py_generic_services = 18 [default=false];
+
+  // Is this file deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for everything in the file, or it will be completely ignored; in the very
+  // least, this is a formalization for deprecating files.
+  optional bool deprecated = 23 [default=false];
+
+  // Enables the use of arenas for the proto messages in this file. This applies
+  // only to generated classes for C++.
+  optional bool cc_enable_arenas = 31 [default=false];
+
+
+  // Sets the objective c class prefix which is prepended to all objective c
+  // generated classes from this .proto. There is no default.
+  optional string objc_class_prefix = 36;
+
+  // Namespace for generated classes; defaults to the package.
+  optional string csharp_namespace = 37;
+
+  // By default Swift generators will take the proto package and CamelCase it
+  // replacing '.' with underscore and use that to prefix the types/symbols
+  // defined. When this options is provided, they will use this value instead
+  // to prefix the types/symbols defined.
+  optional string swift_prefix = 39;
+
+  // Sets the php class prefix which is prepended to all php generated classes
+  // from this .proto. Default is empty.
+  optional string php_class_prefix = 40;
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+
+//  reserved 38;
+}
+
+message MessageOptions {
+  // Set true to use the old proto1 MessageSet wire format for extensions.
+  // This is provided for backwards-compatibility with the MessageSet wire
+  // format.  You should not use this for any other reason:  It's less
+  // efficient, has fewer features, and is more complicated.
+  //
+  // The message must be defined exactly as follows:
+  //   message Foo {
+  //     option message_set_wire_format = true;
+  //     extensions 4 to max;
+  //   }
+  // Note that the message cannot have any defined fields; MessageSets only
+  // have extensions.
+  //
+  // All extensions of your type must be singular messages; e.g. they cannot
+  // be int32s, enums, or repeated messages.
+  //
+  // Because this is an option, the above two restrictions are not enforced by
+  // the protocol compiler.
+  optional bool message_set_wire_format = 1 [default=false];
+
+  // Disables the generation of the standard "descriptor()" accessor, which can
+  // conflict with a field of the same name.  This is meant to make migration
+  // from proto1 easier; new code should avoid fields named "descriptor".
+  optional bool no_standard_descriptor_accessor = 2 [default=false];
+
+  // Is this message deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for the message, or it will be completely ignored; in the very least,
+  // this is a formalization for deprecating messages.
+  optional bool deprecated = 3 [default=false];
+
+  // Whether the message is an automatically generated map entry type for the
+  // maps field.
+  //
+  // For maps fields:
+  //     map map_field = 1;
+  // The parsed descriptor looks like:
+  //     message MapFieldEntry {
+  //         option map_entry = true;
+  //         optional KeyType key = 1;
+  //         optional ValueType value = 2;
+  //     }
+  //     repeated MapFieldEntry map_field = 1;
+  //
+  // Implementations may choose not to generate the map_entry=true message, but
+  // use a native map in the target language to hold the keys and values.
+  // The reflection APIs in such implementions still need to work as
+  // if the field is a repeated message field.
+  //
+  // NOTE: Do not set the option in .proto files. Always use the maps syntax
+  // instead. The option should only be implicitly set by the proto compiler
+  // parser.
+  optional bool map_entry = 7;
+
+//  reserved 8;  // javalite_serializable
+
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+message FieldOptions {
+  // The ctype option instructs the C++ code generator to use a different
+  // representation of the field than it normally would.  See the specific
+  // options below.  This option is not yet implemented in the open source
+  // release -- sorry, we'll try to include it in a future version!
+  optional CType ctype = 1 [default = STRING];
+  enum CType {
+    // Default mode.
+    STRING = 0;
+
+    CORD = 1;
+
+    STRING_PIECE = 2;
+  }
+  // The packed option can be enabled for repeated primitive fields to enable
+  // a more efficient representation on the wire. Rather than repeatedly
+  // writing the tag and type for each element, the entire array is encoded as
+  // a single length-delimited blob. In proto3, only explicit setting it to
+  // false will avoid using packed encoding.
+  optional bool packed = 2;
+
+  // The jstype option determines the JavaScript type used for values of the
+  // field.  The option is permitted only for 64 bit integral and fixed types
+  // (int64, uint64, sint64, fixed64, sfixed64).  By default these types are
+  // represented as JavaScript strings.  This avoids loss of precision that can
+  // happen when a large value is converted to a floating point JavaScript
+  // numbers.  Specifying JS_NUMBER for the jstype causes the generated
+  // JavaScript code to use the JavaScript "number" type instead of strings.
+  // This option is an enum to permit additional types to be added,
+  // e.g. goog.math.Integer.
+  optional JSType jstype = 6 [default = JS_NORMAL];
+  enum JSType {
+    // Use the default type.
+    JS_NORMAL = 0;
+
+    // Use JavaScript strings.
+    JS_STRING = 1;
+
+    // Use JavaScript numbers.
+    JS_NUMBER = 2;
+  }
+
+  // Should this field be parsed lazily?  Lazy applies only to message-type
+  // fields.  It means that when the outer message is initially parsed, the
+  // inner message's contents will not be parsed but instead stored in encoded
+  // form.  The inner message will actually be parsed when it is first accessed.
+  //
+  // This is only a hint.  Implementations are free to choose whether to use
+  // eager or lazy parsing regardless of the value of this option.  However,
+  // setting this option true suggests that the protocol author believes that
+  // using lazy parsing on this field is worth the additional bookkeeping
+  // overhead typically needed to implement it.
+  //
+  // This option does not affect the public interface of any generated code;
+  // all method signatures remain the same.  Furthermore, thread-safety of the
+  // interface is not affected by this option; const methods remain safe to
+  // call from multiple threads concurrently, while non-const methods continue
+  // to require exclusive access.
+  //
+  //
+  // Note that implementations may choose not to check required fields within
+  // a lazy sub-message.  That is, calling IsInitialized() on the outer message
+  // may return true even if the inner message has missing required fields.
+  // This is necessary because otherwise the inner message would have to be
+  // parsed in order to perform the check, defeating the purpose of lazy
+  // parsing.  An implementation which chooses not to check required fields
+  // must be consistent about it.  That is, for any particular sub-message, the
+  // implementation must either *always* check its required fields, or *never*
+  // check its required fields, regardless of whether or not the message has
+  // been parsed.
+  optional bool lazy = 5 [default=false];
+
+  // Is this field deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for accessors, or it will be completely ignored; in the very least, this
+  // is a formalization for deprecating fields.
+  optional bool deprecated = 3 [default=false];
+
+  // For Google-internal migration only. Do not use.
+  optional bool weak = 10 [default=false];
+
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+
+//  reserved 4;  // removed jtype
+}
+
+message OneofOptions {
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+message EnumOptions {
+
+  // Set this option to true to allow mapping different tag names to the same
+  // value.
+  optional bool allow_alias = 2;
+
+  // Is this enum deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for the enum, or it will be completely ignored; in the very least, this
+  // is a formalization for deprecating enums.
+  optional bool deprecated = 3 [default=false];
+
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+message EnumValueOptions {
+  // Is this enum value deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for the enum value, or it will be completely ignored; in the very least,
+  // this is a formalization for deprecating enum values.
+  optional bool deprecated = 1 [default=false];
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+message ServiceOptions {
+
+  // Note:  Field numbers 1 through 32 are reserved for Google's internal RPC
+  //   framework.  We apologize for hoarding these numbers to ourselves, but
+  //   we were already using them long before we decided to release Protocol
+  //   Buffers.
+
+  // Is this service deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for the service, or it will be completely ignored; in the very least,
+  // this is a formalization for deprecating services.
+  optional bool deprecated = 33 [default=false];
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+message MethodOptions {
+
+  // Note:  Field numbers 1 through 32 are reserved for Google's internal RPC
+  //   framework.  We apologize for hoarding these numbers to ourselves, but
+  //   we were already using them long before we decided to release Protocol
+  //   Buffers.
+
+  // Is this method deprecated?
+  // Depending on the target platform, this can emit Deprecated annotations
+  // for the method, or it will be completely ignored; in the very least,
+  // this is a formalization for deprecating methods.
+  optional bool deprecated = 33 [default=false];
+
+  // Is this method side-effect-free (or safe in HTTP parlance), or idempotent,
+  // or neither? HTTP based RPC implementation may choose GET verb for safe
+  // methods, and PUT verb for idempotent methods instead of the default POST.
+  enum IdempotencyLevel {
+    IDEMPOTENCY_UNKNOWN = 0;
+    NO_SIDE_EFFECTS     = 1; // implies idempotent
+    IDEMPOTENT          = 2; // idempotent, but may have side effects
+  }
+  optional IdempotencyLevel idempotency_level =
+      34 [default=IDEMPOTENCY_UNKNOWN];
+
+  // The parser stores options it doesn't recognize here. See above.
+  repeated UninterpretedOption uninterpreted_option = 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
+
+
+// A message representing a option the parser does not recognize. This only
+// appears in options protos created by the compiler::Parser class.
+// DescriptorPool resolves these when building Descriptor objects. Therefore,
+// options protos in descriptor objects (e.g. returned by Descriptor::options(),
+// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions
+// in them.
+message UninterpretedOption {
+  // The name of the uninterpreted option.  Each string represents a segment in
+  // a dot-separated name.  is_extension is true iff a segment represents an
+  // extension (denoted with parentheses in options specs in .proto files).
+  // E.g.,{ ["foo", false], ["bar.baz", true], ["qux", false] } represents
+  // "foo.(bar.baz).qux".
+  message NamePart {
+    required string name_part = 1;
+    required bool is_extension = 2;
+  }
+  repeated NamePart name = 2;
+
+  // The value of the uninterpreted option, in whatever type the tokenizer
+  // identified it as during parsing. Exactly one of these should be set.
+  optional string identifier_value = 3;
+  optional uint64 positive_int_value = 4;
+  optional int64 negative_int_value = 5;
+  optional double double_value = 6;
+  optional bytes string_value = 7;
+  optional string aggregate_value = 8;
+}
+
+// ===================================================================
+// Optional source code info
+
+// Encapsulates information about the original source file from which a
+// FileDescriptorProto was generated.
+message SourceCodeInfo {
+  // A Location identifies a piece of source code in a .proto file which
+  // corresponds to a particular definition.  This information is intended
+  // to be useful to IDEs, code indexers, documentation generators, and similar
+  // tools.
+  //
+  // For example, say we have a file like:
+  //   message Foo {
+  //     optional string foo = 1;
+  //   }
+  // Let's look at just the field definition:
+  //   optional string foo = 1;
+  //   ^       ^^     ^^  ^  ^^^
+  //   a       bc     de  f  ghi
+  // We have the following locations:
+  //   span   path               represents
+  //   [a,i)  [ 4, 0, 2, 0 ]     The whole field definition.
+  //   [a,b)  [ 4, 0, 2, 0, 4 ]  The label (optional).
+  //   [c,d)  [ 4, 0, 2, 0, 5 ]  The type (string).
+  //   [e,f)  [ 4, 0, 2, 0, 1 ]  The name (foo).
+  //   [g,h)  [ 4, 0, 2, 0, 3 ]  The number (1).
+  //
+  // Notes:
+  // - A location may refer to a repeated field itself (i.e. not to any
+  //   particular index within it).  This is used whenever a set of elements are
+  //   logically enclosed in a single code segment.  For example, an entire
+  //   extend block (possibly containing multiple extension definitions) will
+  //   have an outer location whose path refers to the "extensions" repeated
+  //   field without an index.
+  // - Multiple locations may have the same path.  This happens when a single
+  //   logical declaration is spread out across multiple places.  The most
+  //   obvious example is the "extend" block again -- there may be multiple
+  //   extend blocks in the same scope, each of which will have the same path.
+  // - A location's span is not always a subset of its parent's span.  For
+  //   example, the "extendee" of an extension declaration appears at the
+  //   beginning of the "extend" block and is shared by all extensions within
+  //   the block.
+  // - Just because a location's span is a subset of some other location's span
+  //   does not mean that it is a descendent.  For example, a "group" defines
+  //   both a type and a field in a single declaration.  Thus, the locations
+  //   corresponding to the type and field and their components will overlap.
+  // - Code which tries to interpret locations should probably be designed to
+  //   ignore those that it doesn't understand, as more types of locations could
+  //   be recorded in the future.
+  repeated Location location = 1;
+  message Location {
+    // Identifies which part of the FileDescriptorProto was defined at this
+    // location.
+    //
+    // Each element is a field number or an index.  They form a path from
+    // the root FileDescriptorProto to the place where the definition.  For
+    // example, this path:
+    //   [ 4, 3, 2, 7, 1 ]
+    // refers to:
+    //   file.message_type(3)  // 4, 3
+    //       .field(7)         // 2, 7
+    //       .name()           // 1
+    // This is because FileDescriptorProto.message_type has field number 4:
+    //   repeated DescriptorProto message_type = 4;
+    // and DescriptorProto.field has field number 2:
+    //   repeated FieldDescriptorProto field = 2;
+    // and FieldDescriptorProto.name has field number 1:
+    //   optional string name = 1;
+    //
+    // Thus, the above path gives the location of a field name.  If we removed
+    // the last element:
+    //   [ 4, 3, 2, 7 ]
+    // this path refers to the whole field declaration (from the beginning
+    // of the label to the terminating semicolon).
+    repeated int32 path = 1 [packed=true];
+
+    // Always has exactly three or four elements: start line, start column,
+    // end line (optional, otherwise assumed same as start line), end column.
+    // These are packed into a single field for efficiency.  Note that line
+    // and column numbers are zero-based -- typically you will want to add
+    // 1 to each before displaying to a user.
+    repeated int32 span = 2 [packed=true];
+
+    // If this SourceCodeInfo represents a complete declaration, these are any
+    // comments appearing before and after the declaration which appear to be
+    // attached to the declaration.
+    //
+    // A series of line comments appearing on consecutive lines, with no other
+    // tokens appearing on those lines, will be treated as a single comment.
+    //
+    // leading_detached_comments will keep paragraphs of comments that appear
+    // before (but not connected to) the current element. Each paragraph,
+    // separated by empty lines, will be one comment element in the repeated
+    // field.
+    //
+    // Only the comment content is provided; comment markers (e.g. //) are
+    // stripped out.  For block comments, leading whitespace and an asterisk
+    // will be stripped from the beginning of each line other than the first.
+    // Newlines are included in the output.
+    //
+    // Examples:
+    //
+    //   optional int32 foo = 1;  // Comment attached to foo.
+    //   // Comment attached to bar.
+    //   optional int32 bar = 2;
+    //
+    //   optional string baz = 3;
+    //   // Comment attached to baz.
+    //   // Another line attached to baz.
+    //
+    //   // Comment attached to qux.
+    //   //
+    //   // Another line attached to qux.
+    //   optional double qux = 4;
+    //
+    //   // Detached comment for corge. This is not leading or trailing comments
+    //   // to qux or corge because there are blank lines separating it from
+    //   // both.
+    //
+    //   // Detached comment for corge paragraph 2.
+    //
+    //   optional string corge = 5;
+    //   /* Block comment attached
+    //    * to corge.  Leading asterisks
+    //    * will be removed. */
+    //   /* Block comment attached to
+    //    * grault. */
+    //   optional int32 grault = 6;
+    //
+    //   // ignored detached comments.
+    optional string leading_comments = 3;
+    optional string trailing_comments = 4;
+    repeated string leading_detached_comments = 6;
+  }
+}
+
+// Describes the relationship between generated code and its original source
+// file. A GeneratedCodeInfo message is associated with only one generated
+// source file, but may contain references to different source .proto files.
+message GeneratedCodeInfo {
+  // An Annotation connects some span of text in generated code to an element
+  // of its generating .proto file.
+  repeated Annotation annotation = 1;
+  message Annotation {
+    // Identifies the element in the original source .proto file. This field
+    // is formatted the same as SourceCodeInfo.Location.path.
+    repeated int32 path = 1 [packed=true];
+
+    // Identifies the filesystem path to the original source .proto.
+    optional string source_file = 2;
+
+    // Identifies the starting offset in bytes in the generated code
+    // that relates to the identified object.
+    optional int32 begin = 3;
+
+    // Identifies the ending offset in bytes in the generated code that
+    // relates to the identified offset. The end offset should be one past
+    // the last relevant byte (so the length of the text = end - begin).
+    optional int32 end = 4;
+  }
+}
diff --git a/release-notes/VERSION b/release-notes/VERSION
index efb501d88..6abf17077 100644
--- a/release-notes/VERSION
+++ b/release-notes/VERSION
@@ -29,4 +29,6 @@ implementations)
   `SmileGenerator.Feature` as `SmileWriteFeature`
 #543: (avro) Enable "logical types" support in `AvroSchemaGenerator`
   by default (3.0)
+#557: Change 3.0 to use `module-info.java` directly [JSTEP-11]
 - Minimum Java baseline: Java 17
+- (protobuf) Remove shading of `protoparser` dependency
diff --git a/smile/pom.xml b/smile/pom.xml
index d2e16c346..235b44743 100644
--- a/smile/pom.xml
+++ b/smile/pom.xml
@@ -82,13 +82,6 @@ tree model)
           
         
       
-      
-      
-	org.moditect
-	moditect-maven-plugin
-      
     
   
 
diff --git a/smile/src/moditect/module-info.java b/smile/src/main/java/module-info.java
similarity index 84%
rename from smile/src/moditect/module-info.java
rename to smile/src/main/java/module-info.java
index a192f9436..6e5e2f41a 100644
--- a/smile/src/moditect/module-info.java
+++ b/smile/src/main/java/module-info.java
@@ -1,4 +1,6 @@
-module tools.jackson.dataformat.smile {
+// Smile Main artifact Module descriptor
+module tools.jackson.dataformat.smile
+{
     requires tools.jackson.core;
     requires tools.jackson.databind;
 
diff --git a/smile/src/test/java/module-info.java b/smile/src/test/java/module-info.java
new file mode 100644
index 000000000..c889ebac1
--- /dev/null
+++ b/smile/src/test/java/module-info.java
@@ -0,0 +1,28 @@
+// Smile unit test Module descriptor
+module tools.jackson.dataformat.smile
+{
+    // Since we are not split from Main artifact, will not
+    // need to depend on Main artifact -- but need its dependencies
+    
+    requires tools.jackson.core;
+    requires tools.jackson.databind;
+
+    // Additional test lib/framework dependencies
+    requires org.junit.jupiter.api;
+    requires org.junit.jupiter.params;
+
+    // Further, need to open up some packages for JUnit et al
+    opens tools.jackson.dataformat.smile;
+    opens tools.jackson.dataformat.smile.async;
+    opens tools.jackson.dataformat.smile.constraints;
+    opens tools.jackson.dataformat.smile.dos;
+    opens tools.jackson.dataformat.smile.filter;
+    opens tools.jackson.dataformat.smile.fuzz;
+    opens tools.jackson.dataformat.smile.gen;
+    opens tools.jackson.dataformat.smile.gen.dos;
+    opens tools.jackson.dataformat.smile.mapper;
+    opens tools.jackson.dataformat.smile.parse;
+    opens tools.jackson.dataformat.smile.seq;
+    opens tools.jackson.dataformat.smile.testutil;
+    opens tools.jackson.dataformat.smile.testutil.failure;
+}
diff --git a/smile/src/test/java/tools/jackson/dataformat/smile/parse/SymbolTableTest.java b/smile/src/test/java/tools/jackson/dataformat/smile/parse/SymbolTableTest.java
index 56fa2ee32..0faed1ad1 100644
--- a/smile/src/test/java/tools/jackson/dataformat/smile/parse/SymbolTableTest.java
+++ b/smile/src/test/java/tools/jackson/dataformat/smile/parse/SymbolTableTest.java
@@ -44,19 +44,19 @@ public void testSimpleDefault() throws Exception
             assertTrue(syms.isCanonicalizing()); // added in 2.13
 
             assertEquals(0, syms.size());
-            assertEquals(0, _findParent(syms).size());
+            //assertEquals(0, _findParent(syms).size());
 
             assertToken(JsonToken.START_OBJECT, p.nextToken());
             assertToken(JsonToken.PROPERTY_NAME, p.nextToken());
             assertEquals("a", p.currentName());
             assertEquals(1, syms.size());
             // not yet synced to parent
-            assertEquals(0, _findParent(syms).size());
+            //assertEquals(0, _findParent(syms).size());
 
             while (p.nextToken() != null) { ; }
             assertEquals(2, syms.size());
             // but after closing, should sync
-            assertEquals(2, _findParent(syms).size());
+            //assertEquals(2, _findParent(syms).size());
         }
 
         // by default, should canonicalize etc:
@@ -64,7 +64,7 @@ public void testSimpleDefault() throws Exception
             ByteQuadsCanonicalizer syms = _findSymbols(p);
             assertEquals(2, syms.size());
             // also check that parent (root) has it all?
-            assertEquals(2, _findParent(syms).size());
+            //assertEquals(2, _findParent(syms).size());
 
             // but no additions second time around
             while (p.nextToken() != null) { ; }
@@ -128,7 +128,7 @@ public void testSimpleNoCanonicalize() throws Exception
             assertFalse(syms.isCanonicalizing()); // added in 2.13
             assertEquals(-1, syms.size());
             // also, should not have parent:
-            assertNull(_findParent(syms));
+            //assertNull(_findParent(syms));
 
             assertToken(JsonToken.START_OBJECT, p.nextToken());
             assertToken(JsonToken.PROPERTY_NAME, p.nextToken());
@@ -237,10 +237,13 @@ private ByteQuadsCanonicalizer _findSymbols(JsonParser p) throws Exception
         return (ByteQuadsCanonicalizer) f.get(p);
     }
 
+    // Cannot access under JPMS, alas
+    /*
     private ByteQuadsCanonicalizer _findParent(ByteQuadsCanonicalizer sym) throws Exception
     {
         Field f = ByteQuadsCanonicalizer.class.getDeclaredField("_parent");
         f.setAccessible(true);
         return (ByteQuadsCanonicalizer) f.get(sym);
     }
+    */
 }