diff --git a/.azure/build.yml b/.azure/build.yml
index 374edb661..4b16e8e04 100644
--- a/.azure/build.yml
+++ b/.azure/build.yml
@@ -1,5 +1,5 @@
 # JPype CI pipeline
-pr:
+trigger:
   branches:
     include:
     - master
@@ -22,19 +22,19 @@ variables:
 jobs:
 - job: Deps
   pool:
-    vmImage: "ubuntu-16.04"
+    vmImage: "ubuntu-latest"
   steps:
   - template: scripts/ivy.yml
 
 - job: Documentation
   pool:
-    vmImage: "ubuntu-16.04"
+    vmImage: "ubuntu-latest"
   steps:
   - template: scripts/documentation.yml
 
 - job: Coverage
   pool:
-    vmImage: "ubuntu-16.04"
+    vmImage: "ubuntu-latest"
   dependsOn: Deps
   steps:
   - template: scripts/deps.yml
@@ -42,7 +42,7 @@ jobs:
 
 - job: Tracing
   pool:
-    vmImage: "ubuntu-16.04"
+    vmImage: "ubuntu-latest"
   steps:
   - template: scripts/tracing.yml
 
@@ -50,39 +50,39 @@ jobs:
   dependsOn: Deps
   strategy:
     matrix:
-      linux-3.5:
-        imageName: "ubuntu-16.04"
-        python.version: '3.5'
-      linux-3.6:
-        imageName: "ubuntu-16.04"
-        python.version: '3.6'
       linux-3.7:
-        imageName: "ubuntu-16.04"
+        imageName: "ubuntu-latest"
         python.version: '3.7'
       linux-3.8:
-        imageName: "ubuntu-16.04"
+        imageName: "ubuntu-latest"
         python.version: '3.8'
       linux-3.9:
-        imageName: "ubuntu-16.04"
+        imageName: "ubuntu-latest"
         python.version: '3.9'
-      windows-3.5:
-        imageName: "vs2017-win2016"
-        python.version: '3.5'
-      windows-3.6:
-        imageName: "vs2017-win2016"
-        python.version: '3.6'
+      linux-3.10:
+        imageName: "ubuntu-latest"
+        python.version: '3.10'
+      linux-3.11:
+        imageName: "ubuntu-latest"
+        python.version: '3.11'
       windows-3.7:
-        imageName: "vs2017-win2016"
+        imageName: "windows-2019"
         python.version: '3.7'
       windows-3.8:
-        imageName: "vs2017-win2016"
+        imageName: "windows-2019"
         python.version: '3.8'
-        #windows-3.9:
-        #imageName: "vs2017-win2016"
-        #python.version: '3.9'
+      windows-3.9:
+        imageName: "windows-2019"
+        python.version: '3.9'
         #jpypetest.fast: 'true'
+      windows-3.10:
+        imageName: "windows-2019"
+        python.version: '3.10'
+      windows-3.11:
+        imageName: "windows-2019"
+        python.version: '3.11'
       mac-3.9:
-        imageName: "macos-10.14"
+        imageName: "macos-11"
         python.version: '3.9'
         jpypetest.fast: 'true'
 
diff --git a/.azure/release.yml b/.azure/release.yml
index 6d5e04355..6c83ec4c9 100644
--- a/.azure/release.yml
+++ b/.azure/release.yml
@@ -30,11 +30,6 @@ stages:
     strategy:
       matrix:
           # Disabled because it is fetching beta images
-          #        64Bit2010:
-          #          arch: x86_64
-          #          plat: manylinux2010_x86_64
-          #          image: quay.io/pypa/manylinux2010_x86_64
-          #          python.architecture: x64
           #        64Bit2014:
           #          arch: aarch64
           #          plat: manylinux2014_aarch64
@@ -42,13 +37,13 @@ stages:
           #          python.architecture: aarch64
         64Bit:
           arch: x86_64
-          plat: manylinux1_x86_64
-          image: quay.io/pypa/manylinux1_x86_64
+          plat: manylinux2010_x86_64
+          image: quay.io/pypa/manylinux2010_x86_64
           python.architecture: x64
         32Bit:
           arch: i686
-          plat: manylinux1_i686
-          image: quay.io/pypa/manylinux1_i686
+          plat: manylinux2010_i686
+          image: quay.io/pypa/manylinux2010_i686
           python.architecture: x86
     pool:
       vmImage: "ubuntu-latest"
@@ -61,9 +56,6 @@ stages:
     condition: eq(1,1)
     strategy:
       matrix:
-        Python36:
-          python.version: '3.6'
-          python.architecture: 'x64'
         Python37:
           python.version: '3.7'
           python.architecture: 'x64'
@@ -73,8 +65,14 @@ stages:
         Python39:
           python.version: '3.9'
           python.architecture: 'x64'
+        Python310:
+          python.version: '3.10'
+          python.architecture: 'x64'
+        Python311:
+          python.version: '3.11'
+          python.architecture: 'x64'
     pool:
-      vmImage: 'vs2017-win2016'
+      vmImage: "windows-2019"
     steps:
     - template: scripts/deps.yml
     - task: UsePythonVersion@0
@@ -93,16 +91,18 @@ stages:
       python.architecture: 'x64'
     strategy:
       matrix:
-        Python36:
-          python.version: '3.6'
         Python37:
           python.version: '3.7'
         Python38:
           python.version: '3.8'
         Python39:
           python.version: '3.9'
+        Python310:
+          python.version: '3.10'
+        Python311:
+          python.version: '3.11'
     pool:
-      vmImage: "macOS-10.14"
+      vmImage: "macos-11"
     steps:
     - template: scripts/deps.yml
     - script: .azure/scripts/osx-python.sh '$(python.version)'
diff --git a/.azure/scripts/build-wheels.sh b/.azure/scripts/build-wheels.sh
index a419ba303..5b536772a 100755
--- a/.azure/scripts/build-wheels.sh
+++ b/.azure/scripts/build-wheels.sh
@@ -2,11 +2,10 @@
 set -e -x
 
 # Collect the pythons
-pys=(/opt/python/*/bin)
+pys=(/opt/python/cp*/bin)
 
-# Filter out Python 3.4
-pys=(${pys[@]//*34*/})
-pys=(${pys[@]//*27*/})
+# Exclude specific Pythons (3.6)
+pys=(${pys[@]//*36*/})
 
 # Compile wheels
 for PYBIN in "${pys[@]}"; do
diff --git a/.azure/scripts/osx-python.sh b/.azure/scripts/osx-python.sh
index 995aa2cf8..292ce31c2 100755
--- a/.azure/scripts/osx-python.sh
+++ b/.azure/scripts/osx-python.sh
@@ -3,21 +3,28 @@
 PYTHON_VERSION="$1"
 
 case $PYTHON_VERSION in
-3.6)
-  FULL_VERSION=3.6.8
-  ;;
 3.7)
   FULL_VERSION=3.7.9
+  INSTALLER_NAME=python-$FULL_VERSION-macosx10.9.pkg
   ;;
 3.8)
-  FULL_VERSION=3.8.6
+  FULL_VERSION=3.8.10
+  INSTALLER_NAME=python-$FULL_VERSION-macosx10.9.pkg
   ;;
 3.9)
-  FULL_VERSION=3.9.0
+  FULL_VERSION=3.9.12
+  INSTALLER_NAME=python-$FULL_VERSION-macosx10.9.pkg
+  ;;
+3.10)
+  FULL_VERSION=3.10.4
+  INSTALLER_NAME=python-$FULL_VERSION-macos11.pkg
+  ;;
+3.11)
+  FULL_VERSION=3.11.0
+  INSTALLER_NAME=python-$FULL_VERSION-macos11.pkg
   ;;
 esac
 
-INSTALLER_NAME=python-$FULL_VERSION-macosx10.9.pkg
 URL=https://www.python.org/ftp/python/$FULL_VERSION/$INSTALLER_NAME
 
 PY_PREFIX=/Library/Frameworks/Python.framework/Versions
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index aa96d7c33..3dcdfe6e5 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 1.3.0
+current_version = 1.4.1
 commit = True
 tag = False
 parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\_(?P<release>[a-z]+)(?P<build>\d+))?
@@ -28,4 +28,3 @@ values =
 search = Latest Changes:
 replace = Latest Changes:
 	- **{new_version} - {now:%Y-%m-%d}**
-
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 450edf347..4b12f640c 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -13,9 +13,16 @@ sphinx:
 
 # Optionally set the version of Python and requirements required to build your docs
 python:
-  version: 3.8
+  version: "3.8"
   install:
     - method: pip
       path: .
       extra_requirements:
         - docs
+
+build:
+  image: latest
+  apt_packages:
+    - openjdk-8-jdk
+
+
diff --git a/README.rst b/README.rst
index a54b693f8..0e2ddab1c 100644
--- a/README.rst
+++ b/README.rst
@@ -26,6 +26,8 @@ access to the entirety of CPython and Java libraries.
  <https://github.com/jpype-project/jpype>`_
 :Issue tracker: `GitHub Issues
  <https://github.com/jpype-project/jpype/issues>`_
+:Discussions: `GitHub Discussions
+ <https://github.com/jpype-project/jpype/discussions>`_
 :Documentation: `Python Docs`_
 :License: `Apache 2 License`_
 :Build status:  |TestsCI|_ |Docs|_
diff --git a/doc/CHANGELOG.rst b/doc/CHANGELOG.rst
index 20fa2cd4c..aa7d7f8ae 100644
--- a/doc/CHANGELOG.rst
+++ b/doc/CHANGELOG.rst
@@ -4,6 +4,53 @@ Changelog
 This changelog *only* contains changes from the *first* pypi release (0.5.4.3) onwards.
 
 Latest Changes:
+- **1.4.1 - 2022-10-26**
+  
+  - Fixed issue with startJVM changing locale settings.
+
+  - Changes to support Python 3.11
+
+  - Fix truncation of strings on null when using convert strings.
+
+  - Replaced distutil with packaging
+
+
+- **1.4.0 - 2022-05-14**
+
+  - Support for all different buffer type conversions.
+
+    - Improved buffer transfers to numpy as guaranteed to match Java types.
+      However, exact dtype for conversions is os/numpy version dependent.
+
+    - Support for byte order channels on buffer transfers.
+
+    - Byte size for buffers now fixed to Java definitions.
+      
+    - When directly accessing Java arrays using memory view, Python requires a
+      cast from buffers.  Required because Python does not support memory view
+      alterations on non-native sizes.
+
+  - Fix crash when comparing JChar.
+
+     - Order handling for numerical operations with JChar fixed.
+
+  - Improved matching for Java functors based on parameter count.
+
+  - Dropped support for Python 3.5 and 3.6
+
+  - dbapi2 handles drivers that don't support autocommit.
+
+  - Fixed issue when Java classes with dunder methods such as  ``__del__`` 
+    caused conflicts in Python type system.   Java method which match dunder 
+    patterns are longer translated to Python.
+
+  - Fix issue with numpy arrays with no dimensions resulting in crash.
+
+  - Support for user defined conversions for java.lang.Class and array types.
+
+  - Fixed issue with ssize_t on Windows for Python 3.10.
+
+
 - **1.3.0 - 2021-05-19**
 
   - Fixes for memory issues found when upgrading to Python 3.10 beta.
diff --git a/doc/conf.py b/doc/conf.py
index 30d3d2983..24091a14d 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -31,7 +31,10 @@
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
-extensions = ['sphinx.ext.napoleon', 'sphinx.ext.autodoc', 'sphinx.ext.autosectionlabel', 'readthedocs_ext.readthedocs', ]
+extensions = ['sphinx.ext.napoleon',
+        'sphinx.ext.autodoc',
+       'sphinx.ext.autosectionlabel',
+       'readthedocs_ext.readthedocs', ]
 autosectionlabel_prefix_document = True
 
 # Add any paths that contain templates here, relative to this directory.
@@ -236,12 +239,7 @@ def patch(self, key):
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
 html_static_path = ['_static']
-
-html_context = {
-    'css_files': [
-        '_static/theme_overrides.css',  # override wide tables in RTD theme
-    ],
-}
+html_css_files = ['theme_overrides.css']
 
 # Add any extra paths that contain custom files (such as robots.txt or
 # .htaccess) here, relative to this directory. These files are copied
diff --git a/doc/dbapi2.rst b/doc/dbapi2.rst
index fdf9ef521..70b7167fd 100644
--- a/doc/dbapi2.rst
+++ b/doc/dbapi2.rst
@@ -376,7 +376,41 @@ so that the custom return type accurately reflects the column type.
           # Custom return converter here
           return rc
    JSON = JSONType("JSON")
-  
+
+
+Interactions with prepared statements
+-------------------------------------
+
+Certain calls can be problematic for dbapi2 depending on driver.  In
+particular, SQL calls which invalidate the state of the connection will issue
+an exception when the connection is used.   In HSQLDB the statement
+``cur.execute('shutdown')`` invalidates the connection which when the statement
+is then automatically closed will then produce an exception.
+
+This exception is due to a conflict between dbapi2, Java, and HSQLDB
+specifications.  Dbapi2 requires that statements be executed as prepared
+statements, Java requires that closing a statement yields no action if the
+connection is already closed, and HSQLBD sets the ``isValid`` to false but not
+``isClosed``.  Thus executing a shutdown through dbapi2 would be expected to
+close the prepared statement on an invalid connection resulting in an error.
+
+We can address these sort of driver specific behaviors by applying a customizer
+to the Java class to add additional behaviors.
+
+.. code-block:: python
+
+        @jpype.JImplementationFor("java.sql.PreparedStatement")
+        class MyStatement(object):
+            @jpype.JOverride(sticky=True, rename='_close')
+            def close(self):
+                if not self.getConnection().isValid(100):
+                     return
+                return self._close()
+
+Alternatively we can access the ``java.sql.Connection`` directly and call the
+shutdown routine using an unprepared statement.  Though that would require
+accessing private fields.
+
 
 Conclusion
 ==========
diff --git a/doc/java/lang.py b/doc/java/lang.py
index efd2f4591..371aeb3a6 100644
--- a/doc/java/lang.py
+++ b/doc/java/lang.py
@@ -25,7 +25,7 @@ class AutoCloseable:
 
     .. code-block:: python
 
-        from java.nio.files import Files, Paths
+        from java.nio.file import Files, Paths
         with Files.newInputStream(Paths.get("foo")) as fd:
           # operate on the input stream
 
diff --git a/doc/quickguide.py b/doc/quickguide.py
index 7384e3711..4bb6c8453 100644
--- a/doc/quickguide.py
+++ b/doc/quickguide.py
@@ -43,14 +43,6 @@ def python(s):
 """ % s
 
 
-def generic(s):
-    return """
-.. code-block::
-
-    %s
-""" % s
-
-
 def entry(Desc=None, Java=None, Python=None, Notes=None):
     global footnotes, footnotecounter
     if not Java:
@@ -120,7 +112,7 @@ def entry(Desc=None, Java=None, Python=None, Notes=None):
 
     package org.pkg;
 
-    publc class BaseClass
+    public class BaseClass
     {
        public void callMember(int i)
        {}
@@ -296,7 +288,7 @@ def entry(Desc=None, Java=None, Python=None, Notes=None):
 # Casting
 entry("Casting to a specific type",
       java("BaseClass b = (BaseClass)myObject;"),
-      generic("b = (BaseClass) @ myObject"),
+      python("b = (BaseClass) @ myObject"),
       "Matmul(@) is used as the casting operator.")
 endSection()
 
@@ -703,7 +695,7 @@ def call(self):
 
 entry("Lambdas",
         java('DoubleUnaryOperator u = (p->p*2);'),
-        generic('u=DoubleUnaryOperator@(lambda x: x*2)'),
+        python('u=DoubleUnaryOperator@(lambda x: x*2)'),
         'Any Java functional interface can take a lambda or callable.')
 endSection()
 
diff --git a/doc/quickguide.rst b/doc/quickguide.rst
index c748eacaf..536b5f688 100644
--- a/doc/quickguide.rst
+++ b/doc/quickguide.rst
@@ -248,7 +248,7 @@ module, loaded using ``JPackage`` or loaded with the ``JClass`` factory.
 |                           |                                                         |                                                         |
 +---------------------------+---------------------------------------------------------+---------------------------------------------------------+
 |                           |                                                         |                                                         |
-| Casting to a specific     | .. code-block:: java                                    | .. code-block::                                         |
+| Casting to a specific     | .. code-block:: java                                    | .. code-block:: python                                  |
 | type [11]_                |                                                         |                                                         |
 |                           |     BaseClass b = (BaseClass)myObject;                  |     b = (BaseClass) @ myObject                          |
 |                           |                                                         |                                                         |
@@ -706,7 +706,7 @@ with an interface for each methods that are to be accessed from Python.
 | Extending classes [23]_   |                                                         |                                                         |
 +---------------------------+---------------------------------------------------------+---------------------------------------------------------+
 |                           |                                                         |                                                         |
-| Lambdas [24]_             | .. code-block:: java                                    | .. code-block::                                         |
+| Lambdas [24]_             | .. code-block:: java                                    | .. code-block:: python                                  |
 |                           |                                                         |                                                         |
 |                           |     DoubleUnaryOperator u = (p->p*2);                   |     u=DoubleUnaryOperator@(lambda x: x*2)               |
 |                           |                                                         |                                                         |
diff --git a/doc/userguide.rst b/doc/userguide.rst
index 31fdd38b5..f7ba9a060 100644
--- a/doc/userguide.rst
+++ b/doc/userguide.rst
@@ -92,11 +92,11 @@ set the class path, start the JVM, remove all the type declarations, and you are
 
    # Copy in the patterns from the guide to replace the example code
    db = Database("our_records")
-   with  db.connect() as DatabaseConnection:
-      c.runQuery()
-      while c.hasRecords():
-          record = db.nextRecord()
-          ...
+   with db.connect() as c:
+       c.runQuery()
+       while c.hasRecords():
+           record = db.nextRecord()
+           ...
 
 Launch it in the interactive window.  You can get back to programming in Python
 once you get a good night sleep.
@@ -136,11 +136,11 @@ in your serialized object.
    from java.nio.file import Files, Paths
    from java.io import ObjectInputStream
 
-   with Files.newInputStream(Paths.get("myobject.ser") as stream:
-      ois = new ObjectInputStream(stream)
-      obj = ois.readObject()
+   with Files.newInputStream(Paths.get("myobject.ser")) as stream:
+       ois = ObjectInputStream(stream)
+       obj = ois.readObject()
 
-   print(obj) # prints org.bigstuff.MyObject@7382f612
+   print(obj)  # prints org.bigstuff.MyObject@7382f612
 
 It appears that the structure is loaded.  The problematic structure requires you
 call the getData method with the correct index.
@@ -172,7 +172,7 @@ example and watch what happens.
 
 .. code-block:: python
 
-   import matplot.pyplot as plt
+   import matplotlib.pyplot as plt
    plt.plot(d)
    plt.show()
 
@@ -227,13 +227,13 @@ Based on the previous examples, you start by defining a monitor class
 
   @JImplements(Monitor)
   class HeartMonitor:
-     def __init__(self):
-        self.readings = []
-     @JOverride
-     def onMeasurement(self, measurement):
-        self.readings.append([measurement.getTime(), measurement.getHeartRate()])
-     def getResults(self):
-        return np.array(self.readings)
+      def __init__(self):
+          self.readings = []
+      @JOverride
+      def onMeasurement(self, measurement):
+          self.readings.append([measurement.getTime(), measurement.getHeartRate()])
+      def getResults(self):
+          return np.array(self.readings)
 
 There is a bit to unpack here.  You have implemented a Java class from within Python.
 The Java implementation is simply an ordinary Python class which has be
@@ -742,8 +742,9 @@ Details on the standard conversions provided by JPype are given in the section
 Java casting
 ------------
 
-To access a casting operation we use the casting ``JObject`` wrapper.
-JObject accepts two arguments.  The first argument is the object to convert and
+To access a casting operation we use the casting ``JObject`` wrapper.  
+For example, ``JObject(object, Type)`` would produce a copy with specificed type.
+The first argument is the object to convert and
 the second is the type to cast to.  The second argument should always be a Java
 type specified using a class wrapper, a Java class instance, or a string.
 Casting will also add a hidden class argument to the resulting object such that
@@ -758,6 +759,24 @@ general JPype constructors only provide access the Java constructor methods
 that are defined in the Java documentation.  Casting on the other hand is
 entirely the domain of whatever JPype has defined including user defined casts.
 
+As ``JObject`` syntax is long and does not look much like Java syntax, the
+Python matmul operator is overloaded on JPype types such that one can use the
+``@`` operator to cast to a specific Java type.   In Java, one would write
+``(Type)object`` to cast the variable ``object`` to ``Type``.  In Python, this
+would be written as ``Type@object``.   This can also be applied to array types
+``JLong[:]@[1,2,3]``, collection types ``Iterable@[1,2,3]`` or Java functors
+``DoubleUnaryOperator@(lambda x:x*2)``.  The result of the casting operator
+will be a Java object with the desired type or raise a ``TypeError`` if the
+cast or conversion is not possible.   For Python objects, the Java object will
+generally be a copy as it is not possible to reflect changes in an array back
+to Python.  If one needs to retrieve the resulting changes keep a copy of the
+converted array before passing it.  For an existing Java object, casting
+changes the resolution type for the object.  This can be very useful when
+trying to call a specific method overload.   For example, if we have a Java
+``a=String("hello")`` and there were an overload of the method ``foo`` between
+``String`` and ``Object`` we would need to select the overload with
+``foo(java.lang.Object@a)``.  
+
 .. _JObject:
 
 Casting is performed through the Python class ``JObject``.  JObject is called
@@ -778,12 +797,19 @@ indicate that the object is not available.  The equivalent concept in Python is
 ``None``.  Thus all methods that accept any object type that permit a null will
 accept None as an augment with implicit conversion.  However, sometime it is
 necessary to pass an explicit type to the method resolution.  To achieve this
-in JPype use ``JObject(None, type)`` which will create a null pointer with the
+in JPype use ``Type@None`` which will create a null pointer with the
 desired type.  To test if something is null we have to compare the handle to
 None.  This unfortunately trips up some code quality checkers.  The idiom in
 Python is ``obj is None``, but as this only matches things that Python
 considers identical, we must instead use ``obj==None``.
 
+Casting ``None`` is use to specify types when calling between overloads
+with variadic arguments such as ``foo(Object a)`` and ``foo(Object... many)``.
+If we want to call ``foo(None)`` is is ambiguous whether we intend to call the
+first with a null object or the second with a null array.  We can resolve the
+ambiguity with ``foo(java.lang.Object@None)`` or
+``foo(java.lang.Object[:]@None)``
+
 Type enforcement appears in three different places within JPype.  These are
 whenever a Java method is called, whenever a Java field is set, and whenever
 Python returns a value back to Java.
@@ -1080,6 +1106,17 @@ sequence which hold the elements of the array.  If the members of the
 initializer sequence are not Java members then each will be converted.  If
 any element cannot be converted a ``TypeError`` will be raised.
 
+As a shortcut the ``[]`` operator can be used to specify an array type or
+an array instance.   For example, ``JInt[5]`` will allocate an array instance
+of Java ints with length 5.  ``JInt[:]`` will create a type instance with 
+an unspecific length which can be used for the casting operator.  To create
+an array instance with multiple dimensions we would use ``JInt[5,10]`` 
+which would create a rectangular array which was 5 by 10.   To create a
+jagged array we would substitute ``:`` for the final dimensions.  So
+``JInt[5,:]`` is a length 5 array of an array of ``int[]``.  Multidimensional
+array types are specificed like ``JInt[:,:,:]`` would be a Java type
+``int[][][]``.  This applied to both primitive and object types.
+
 JArray is an abstract base class for all Java classes that are produced.
 Thus, one can test if something is an array class using ``issubclass``
 and if Java object is an array using ``isinstance``.
@@ -1146,6 +1183,30 @@ to use the Java array utilities class ``java.util.Arrays`` as it has many
 methods that provide additional functionality.  Java arrays do not support any
 additional mathematical operations at this time.
 
+Creating a Java array is also required when pass by reference syntax is required.
+For example, if a Java function takes an array, modifies it and we want to
+retrieve those values.  In Java, all parameters are pass by value, but the contents
+of a container like an array can be modified which gives the appearance of 
+pass by reference.  For example.
+
+.. code-block:: java
+
+     public void modifies(int[] v) {
+         for (int i=0; i<v.length; ++i)
+              v[i]*=2;
+     }
+
+.. code-block:: python
+
+     orig = [1,2,3]
+     obj = jpype.JInt[:](orig)
+     a.modifies(obj)   #  modifies the array by multiply all by 2
+     orig[:] = obj     #  copy all the values back from Java to Python
+
+If we were to call modifies on the original Python list directly, the temporary copy
+would have been modified so the results would have been lost.
+
+
 Buffer classes
 --------------
 
@@ -1595,28 +1656,28 @@ Here is an example:
 .. code-block:: python
 
   try :
-        # Code that throws a java.lang.RuntimeException
+      # Code that throws a java.lang.RuntimeException
   except java.lang.RuntimeException as ex:
-        print("Caught the runtime exception : ", str(ex))
-        print(ex.stacktrace())
+      print("Caught the runtime exception : ", str(ex))
+      print(ex.stacktrace())
 
 Multiple java exceptions can be caught together or separately:
 
 .. code-block:: python
 
   try:
-        # ...
+      # ...
   except (java.lang.ClassCastException, java.lang.NullPointerException) as ex:
-        print("Caught multiple exceptions : ", str(ex))
-        print(ex.stacktrace())
+      print("Caught multiple exceptions : ", str(ex))
+      print(ex.stacktrace())
   except java.lang.RuntimeException as ex:
-        print("Caught runtime exception : ", str(ex))
-        print(ex.stacktrace())
-  except jpype.JException:
-        print("Caught base exception : ", str(ex))
-        print(ex.stacktrace())
+      print("Caught runtime exception : ", str(ex))
+      print(ex.stacktrace())
+  except jpype.JException as ex:
+      print("Caught base exception : ", str(ex))
+      print(ex.stacktrace())
   except Exception as ex:
-        print("Caught python exception :", str(ex))
+      print("Caught python exception :", str(ex))
 
 Exceptions can be raised in proxies to throw an exception back to Java.
 
@@ -1842,7 +1903,7 @@ should be attached to the Java Runtime object.  The following pattern is used:
     class MyShutdownHook:
         @JOverride
         def run(self):
-           # perform any required shutdown activities
+            # perform any required shutdown activities
 
     java.lang.Runtime.getRuntime().addShutdownHook(Thread(MyShutdownHook()))
 
@@ -1914,16 +1975,16 @@ Example taken from JPype ``java.util.Map`` customizer:
   @_jcustomizer.JImplementationFor('java.util.Map')
   class _JMap:
       def __jclass_init__(self):
-         Mapping.register(self)
+          Mapping.register(self)
 
       def __len__(self):
-         return self.size()
+          return self.size()
 
       def __iter__(self):
-         return self.keySet().iterator()
+          return self.keySet().iterator()
 
       def __delitem__(self, i):
-         return self.remove(i)
+          return self.remove(i)
 
 
 The name of the class does not matter for the purposes of customizer though it
@@ -2294,19 +2355,19 @@ dispatch:
 
     @JImplements(JavaInterface)
     class MyImpl:
-       @JOverride
-       def callOverloaded(self, *args):
-         # always use the wild card args when implementing a dispatch
-         if len(args)==2:
-            return self.callMethod1(*args)
-         if len(args)==1 and isinstance(args[0], JString):
-            return self.callMethod2(*args)
-         raise RuntimeError("Incorrect arguments")
+        @JOverride
+        def callOverloaded(self, *args):
+            # always use the wild card args when implementing a dispatch
+            if len(args)==2:
+                return self.callMethod1(*args)
+            if len(args)==1 and isinstance(args[0], JString):
+                return self.callMethod2(*args)
+            raise RuntimeError("Incorrect arguments")
 
        def callMethod1(self, a1, a2):
-         # ...
+            # ...
        def callMethod2(self, jstr):
-         # ...
+            # ...
 
 Multiple interfaces
 -------------------
@@ -2327,7 +2388,7 @@ achieve this, specify the interface using a string and add the keyword argument
 
     @JImplements("org.foo.JavaInterface", deferred=True)
     class MyImpl:
-       # ...
+        # ...
 
 
 Deferred proxies are not checked at declaration time, but instead at the time
@@ -2383,27 +2444,27 @@ First, with an object:
 
 .. code-block:: python
 
-  class C :
-          def testMethod(self) :
-                  return 42
+  class C:
+      def testMethod(self):
+          return 42
 
-          def testMethod2(self) :
-                  return "Bar"
+      def testMethod2(self):
+          return "Bar"
 
   c = C()  # create an instance
-  proxy = JProxy("ITestInterface2", inst=c) # Convert it into a proxy
+  proxy = JProxy("ITestInterface2", inst=c)  # Convert it into a proxy
 
 or you can use a dictionary.
 
 .. code-block:: python
 
-    def _testMethod() :
-       return 32
+    def _testMethod():
+        return 32
 
-    def _testMethod2() :
-       return "Fooo!"
+    def _testMethod2():
+        return "Fooo!"
 
-    d = { 'testMethod' : _testMethod, 'testMethod2' : _testMethod2, }
+    d = { 'testMethod': _testMethod, 'testMethod2': _testMethod2, }
     proxy = JProxy("ITestInterface2", dict=d)
 
 
@@ -2487,19 +2548,19 @@ launches it from within Python.
     from javax.swing import *
 
     def createAndShowGUI():
-	frame = JFrame("HelloWorldSwing")
-	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
-	label = JLabel("Hello World")
-	frame.getContentPane().add(label)
-	frame.pack()
-	frame.setVisible(True)
+        frame = JFrame("HelloWorldSwing")
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
+        label = JLabel("Hello World")
+        frame.getContentPane().add(label)
+        frame.pack()
+        frame.setVisible(True)
 
     # Start an event loop thread to handling gui events
     @jpype.JImplements(java.lang.Runnable)
     class Launch:
-	@jpype.JOverride
-	def run(self):
-	    createAndShowGUI()
+        @jpype.JOverride
+        def run(self):
+            createAndShowGUI()
     javax.swing.SwingUtilities.invokeLater(Launch())
 
 
@@ -2597,12 +2658,12 @@ keep the synchronization on as long as the object is kept alive.  For example,
     mySharedList = java.util.ArrayList()
 
     # Give the list to another thread that will be adding items
-    otherThread,setList(mySharedList)
+    otherThread.setList(mySharedList)
 
     # Lock the list so that we can access it without interference
     with synchronized(mySharedList):
-       if not mySharedList.isEmpty():
-         ... # process elements
+        if not mySharedList.isEmpty():
+            ...  # process elements
     # Resource is unlocked once we leave the block
 
 The Python ``with`` statement is used to control the scope.  Do not
@@ -2634,26 +2695,26 @@ results.  For example:
 
 .. code-block:: python
 
-  def limit(method, timeout):
-  """ Convert a Java method to asynchronous call with a specified timeout. """
-      def f(*args):
-           @jpype.JImplements(java.util.concurrent.Callable)
-           class g:
-               @jpype.JOverride
-               def call(self):
-                   return method(*args)
-           future = java.util.concurrent.FutureTask(g())
-           java.lang.Thread(future).start()
-           try:
-               timeunit = java.util.concurrent.TimeUnit.MILLISECONDS
-               return future.get(int(timeout*1000), timeunit)
-           except java.util.concurrent.TimeoutException as ex:
-               future.cancel(True)
-           raise RuntimeError("canceled", ex)
-      return f
-
-      print(limit(java.lang.Thread.sleep, timeout=1)(200))
-      print(limit(java.lang.Thread.sleep, timeout=1)(20000))
+    def limit(method, timeout):
+        """ Convert a Java method to asynchronous call with a specified timeout. """
+        def f(*args):
+            @jpype.JImplements(java.util.concurrent.Callable)
+            class g:
+                @jpype.JOverride
+                def call(self):
+                    return method(*args)
+            future = java.util.concurrent.FutureTask(g())
+            java.lang.Thread(future).start()
+            try:
+                timeunit = java.util.concurrent.TimeUnit.MILLISECONDS
+                return future.get(int(timeout*1000), timeunit)
+            except java.util.concurrent.TimeoutException as ex:
+                future.cancel(True)
+            raise RuntimeError("canceled", ex)
+        return f
+
+    print(limit(java.lang.Thread.sleep, timeout=1)(200))
+    print(limit(java.lang.Thread.sleep, timeout=1)(20000))
 
 Here we have limited the execution time of a Java call.
 
@@ -3111,6 +3172,36 @@ JPype Known limitations
 This section lists those limitations that are unlikely to change, as they come
 from external sources.
 
+Annotations
+-----------
+
+Some frameworks such as Spring use Java annotations to indicate specific
+actions.  These may be either runtime annotations or compile time annotations.
+Occasionally while using JPype someone would like to add a Java annotation to a
+JProxy method so that a framework like Spring can pick up that annotation.
+
+JPype uses the Java supplied ``Proxy`` to implement an interface.  That API
+does not support addition of a runtime annotation to a method or class.  Thus,
+all methods and classes when probed with reflection that are implemented in
+Python will come back with no annotations.   
+
+Further, the majority of annotation magic within Java is actually performed at
+compile time.  This is accomplished using an annotation processor.  When a
+class or method is annotated, the compiler checks to see if there is an
+annotation processor which then can produce new code or modify the class
+annotations.  As this is a compile time process, even if annotations were added
+by Python to a class they would still not be active as the corresponding
+compilation phase would not have been executed.   
+
+This is a limitation of the implementation of annotations by the Java virtual
+machine.  It is technically possible though the use of specialized code
+generation with the ASM library or other code generation to add a runtime
+annotation.  Or through exploits of the Java virtual machine annotation
+implementation one can add annotation to existing Java classes.  But these
+annotations are unlikely to be useful. As such JPype will not be able to
+support class or method annotations.
+
+
 Restarting the JVM
 -------------------
 
@@ -3177,7 +3268,7 @@ this block as a fixture at the start of the test suite.
         faulthandler.enable()
         faulthandler.disable()
     except:
-       pass
+        pass
 
 This code enables fault handling and then returns the default handlers which
 will point back to those set by Java.
diff --git a/jpype/__init__.py b/jpype/__init__.py
index 692b88263..573b35f06 100644
--- a/jpype/__init__.py
+++ b/jpype/__init__.py
@@ -51,7 +51,7 @@
 __all__.extend(_jcustomizer.__all__)
 __all__.extend(_gui.__all__)
 
-__version__ = "1.3.0"
+__version__ = "1.4.1"
 __version_info__ = __version__.split('.')
 
 
diff --git a/jpype/_core.py b/jpype/_core.py
index 4e12a6a0b..702a3d158 100644
--- a/jpype/_core.py
+++ b/jpype/_core.py
@@ -44,14 +44,6 @@ class JVMNotRunning(RuntimeError):
     pass
 
 
-def versionTest():
-    if sys.version_info < (3,):
-        raise ImportError("Python 2 is not supported")
-
-
-versionTest()
-
-
 # Activate jedi tab completion
 try:
     import jedi as _jedi
@@ -223,9 +215,22 @@ def startJVM(*args, **kwargs):
                         % (','.join([str(i) for i in kwargs])))
 
     try:
+        import locale
+        # Gather a list of locale settings that Java may override (excluding LC_ALL)
+        categories = [getattr(locale, i) for i in dir(locale) if i.startswith('LC_') and i != 'LC_ALL']
+        # Keep the current locale settings, else Java will replace them.
+        prior = [locale.getlocale(i) for i in categories]
+        # Start the JVM
         _jpype.startup(jvmpath, tuple(args),
                        ignoreUnrecognized, convertStrings, interrupt)
+        # Collect required resources for operation
         initializeResources()
+        # Restore locale
+        for i, j in zip(categories, prior):
+            try:
+                locale.setlocale(i, j)
+            except locale.Error:
+                pass
     except RuntimeError as ex:
         source = str(ex)
         if "UnsupportedClassVersion" in source:
@@ -347,8 +352,6 @@ def _JTerminate():
     try:
         import jpype.config
         # We are exiting anyway so no need to free resources
-        if _jpype.isStarted():
-            _jpype.JPypeContext.freeResources = False
         if jpype.config.onexit:
             _jpype.shutdown(jpype.config.destroy_jvm, False)
     except RuntimeError:
diff --git a/jpype/_jclass.py b/jpype/_jclass.py
index 1840c4590..9c33622d0 100644
--- a/jpype/_jclass.py
+++ b/jpype/_jclass.py
@@ -124,7 +124,9 @@ def _jclassPre(name, bases, members):
         k2 = pysafe(k)
         if k2 != k:
             del members[k]
-            members[k2] = v
+            # Remove unmappable functions
+            if k2:
+                members[k2] = v
 
     # Apply customizers
     hints = _jcustomizer.getClassHints(name)
diff --git a/jpype/_jproxy.py b/jpype/_jproxy.py
index 2efa79df4..8c484cae7 100644
--- a/jpype/_jproxy.py
+++ b/jpype/_jproxy.py
@@ -59,6 +59,9 @@ def _createJProxyDeferred(cls, *intf):
     @JOverride notation on methods evaluated at first
     instantiation.
     """
+    if not isinstance(cls, type):
+        raise TypeError("JImplements only applies to types, not %s" % (type(cls)))
+
     def new(tp, *args, **kwargs):
         # Attach a __jpype_interfaces__ attribute to this class if
         # one doesn't already exist.
@@ -77,6 +80,9 @@ def _createJProxy(cls, *intf):
     """ (internal) Create a proxy from a Python class with
     @JOverride notation on methods evaluated at declaration.
     """
+    if not isinstance(cls, type):
+        raise TypeError("JImplements only applies to types, not %s" % (type(cls)))
+
     actualIntf = _prepareInterfaces(cls, intf)
 
     def new(tp, *args, **kwargs):
diff --git a/jpype/_jvmfinder.py b/jpype/_jvmfinder.py
index e738ee48b..64b656c2b 100644
--- a/jpype/_jvmfinder.py
+++ b/jpype/_jvmfinder.py
@@ -320,10 +320,10 @@ def _javahome_binary(self):
         """
         import platform
         import subprocess
-        from distutils.version import StrictVersion
+        from packaging.version import Version
 
-        current = StrictVersion(platform.mac_ver()[0][:4])
-        if current >= StrictVersion('10.6') and current < StrictVersion('10.9'):
+        current = Version(platform.mac_ver()[0][:4])
+        if current >= Version('10.6') and current < Version('10.9'):
             return subprocess.check_output(
                 ['/usr/libexec/java_home']).strip()
 
diff --git a/jpype/_pykeywords.py b/jpype/_pykeywords.py
index 18ee2132c..0c22631b1 100644
--- a/jpype/_pykeywords.py
+++ b/jpype/_pykeywords.py
@@ -16,8 +16,9 @@
 #
 # *****************************************************************************
 
-# This is a super set of the keywords in Python2 and Python3.
+# This is a superset of the keywords in Python.
 # We use this so that jpype is a bit more version independent.
+# Removing keywords from this list impacts the exposed interfaces, and therefore is a breaking change. 
 _KEYWORDS = set((
     'False', 'None', 'True', 'and', 'as', 'assert', 'async',
     'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else',
@@ -28,6 +29,8 @@
 
 
 def pysafe(s):
+    if s.startswith("__"):
+        return None
     if s in _KEYWORDS:
         return s + "_"
     return s
diff --git a/jpype/dbapi2.py b/jpype/dbapi2.py
index ea763866a..0541fe02d 100644
--- a/jpype/dbapi2.py
+++ b/jpype/dbapi2.py
@@ -452,7 +452,13 @@ def __init__(self, jconnection, adapters, converters, setters, getters):
         self._jcx = jconnection
         # Required by PEP 249
         # https://www.python.org/dev/peps/pep-0249/#commit
-        self._jcx.setAutoCommit(False)
+        try:
+            # Some driver do not support this feature.
+            # https://github.com/jpype-project/jpype/issues/1003
+            self._jcx.setAutoCommit(False)
+        except Exception as ex:
+            if ex.getClass().getSimpleName() != 'SQLFeatureNotSupportedException':
+                raise ex
 
         # Handle defaults
         if adapters is _default:
diff --git a/jpype/imports.py b/jpype/imports.py
index dfc53a167..d9a68ee81 100644
--- a/jpype/imports.py
+++ b/jpype/imports.py
@@ -24,8 +24,6 @@
 ``net`` and ``edu``. Java symbols from these domains can be imported using the
 standard Python syntax.
 
-Import customizers are supported in Python 3.6 or greater.
-
 Forms supported:
    - **import <java_pkg> [ as <name> ]**
    - **import <java_pkg>.<java_class> [ as <name> ]**
@@ -36,9 +34,6 @@
 
 For further information please read the :doc:`imports` guide.
 
-Requires:
-    Python 3.6 or later
-
 Example:
 
 .. code-block:: python
diff --git a/jpype/pickle.py b/jpype/pickle.py
index 34726a083..13eb97a95 100644
--- a/jpype/pickle.py
+++ b/jpype/pickle.py
@@ -49,9 +49,6 @@
 
 Proxies and other JPype specific module resources cannot be pickled currently.
 
-Requires:
-    Python 3.6 or later
-
 """
 from __future__ import absolute_import
 import _jpype
@@ -89,8 +86,7 @@ def __init__(self, dispatch):
         # Extension dispatch table holds reduce method
         self._call = self.reduce
 
-    # Python2 and Python3 _Pickler use get()
-
+    # Pure Python _Pickler uses get()
     def get(self, cls):
         if not issubclass(cls, (_jpype.JClass, _jpype.JObject)):
             return self._dispatch.get(cls)
@@ -102,7 +98,6 @@ def __getitem__(self, cls):
             return self._dispatch[cls]
         return self._call
 
-    # For Python3
     def reduce(self, obj):
         byte = bytes(self._encoder.pack(obj))
         return (self._builder, (byte, ))
diff --git a/jpype/protocol.py b/jpype/protocol.py
index fc77854d0..bf447ac39 100644
--- a/jpype/protocol.py
+++ b/jpype/protocol.py
@@ -105,18 +105,6 @@ def _JInstantConversion(jcls, obj):
     return jcls.ofEpochSecond(sec, nsec)
 
 
-if sys.version_info < (3, 6):  # pragma: no cover
-    import pathlib
-
-    @_jcustomizer.JConversion("java.nio.file.Path", instanceof=pathlib.PurePath)
-    def _JPathConvert(jcls, obj):
-        Paths = _jpype.JClass("java.nio.file.Paths")
-        return Paths.get(str(obj))
-
-    @_jcustomizer.JConversion("java.io.File", instanceof=pathlib.PurePath)
-    def _JFileConvert(jcls, obj):
-        return jcls(str(obj))
-
 # Types needed for SQL
 
 
diff --git a/native/common/include/jp_booleantype.h b/native/common/include/jp_booleantype.h
index b0215ad49..6b19e0954 100755
--- a/native/common/include/jp_booleantype.h
+++ b/native/common/include/jp_booleantype.h
@@ -84,7 +84,7 @@ class JPBooleanType : public JPPrimitiveType
 	virtual void getView(JPArrayView& view) override;
 	virtual void releaseView(JPArrayView& view) override;
 	virtual const char* getBufferFormat() override;
-	virtual ssize_t getItemSize() override;
+	virtual Py_ssize_t getItemSize() override;
 	virtual void copyElements(JPJavaFrame &frame,
 			jarray a, jsize start, jsize len,
 			void* memory, int offset) override;
@@ -94,4 +94,4 @@ class JPBooleanType : public JPPrimitiveType
 
 } ;
 
-#endif // _JP_BOOLEAN_TYPE_H_
\ No newline at end of file
+#endif // _JP_BOOLEAN_TYPE_H_
diff --git a/native/common/include/jp_bytetype.h b/native/common/include/jp_bytetype.h
index 0f35aec9d..d4ee8586d 100755
--- a/native/common/include/jp_bytetype.h
+++ b/native/common/include/jp_bytetype.h
@@ -87,7 +87,7 @@ class JPByteType : public JPPrimitiveType
 	virtual void getView(JPArrayView& view) override;
 	virtual void releaseView(JPArrayView& view) override;
 	virtual const char* getBufferFormat() override;
-	virtual ssize_t getItemSize() override;
+	virtual Py_ssize_t getItemSize() override;
 	virtual void copyElements(JPJavaFrame &frame,
 			jarray a, jsize start, jsize len,
 			void* memory, int offset) override;
@@ -100,4 +100,4 @@ class JPByteType : public JPPrimitiveType
 	static const jlong _Byte_Max = -128;
 } ;
 
-#endif // _JPBYTE_TYPE_H_
\ No newline at end of file
+#endif // _JPBYTE_TYPE_H_
diff --git a/native/common/include/jp_chartype.h b/native/common/include/jp_chartype.h
index acd140105..730d57f22 100755
--- a/native/common/include/jp_chartype.h
+++ b/native/common/include/jp_chartype.h
@@ -80,7 +80,7 @@ class JPCharType : public JPPrimitiveType
 	virtual void getView(JPArrayView& view) override;
 	virtual void releaseView(JPArrayView& view) override;
 	virtual const char* getBufferFormat() override;
-	virtual ssize_t getItemSize() override;
+	virtual Py_ssize_t getItemSize() override;
 	virtual void copyElements(JPJavaFrame &frame,
 			jarray a, jsize start, jsize len,
 			void* memory, int offset) override;
@@ -90,4 +90,4 @@ class JPCharType : public JPPrimitiveType
 
 } ;
 
-#endif // _JP-CHAR_TYPE_H_
\ No newline at end of file
+#endif // _JP-CHAR_TYPE_H_
diff --git a/native/common/include/jp_doubletype.h b/native/common/include/jp_doubletype.h
index 9e0e6222f..209a204d2 100755
--- a/native/common/include/jp_doubletype.h
+++ b/native/common/include/jp_doubletype.h
@@ -84,7 +84,7 @@ class JPDoubleType : public JPPrimitiveType
 	virtual void getView(JPArrayView& view) override;
 	virtual void releaseView(JPArrayView& view) override;
 	virtual const char* getBufferFormat() override;
-	virtual ssize_t getItemSize() override;
+	virtual Py_ssize_t getItemSize() override;
 	virtual void copyElements(JPJavaFrame &frame,
 			jarray a, jsize start, jsize len,
 			void* memory, int offset) override;
@@ -93,4 +93,4 @@ class JPDoubleType : public JPPrimitiveType
 			JPPyBuffer& view, int subs, int base, jobject dims) override;
 } ;
 
-#endif // _JP_DOUBLE_TYPE_H_
\ No newline at end of file
+#endif // _JP_DOUBLE_TYPE_H_
diff --git a/native/common/include/jp_floattype.h b/native/common/include/jp_floattype.h
index 56edb90f9..b0c73f4cf 100755
--- a/native/common/include/jp_floattype.h
+++ b/native/common/include/jp_floattype.h
@@ -84,7 +84,7 @@ class JPFloatType : public JPPrimitiveType
 	virtual void getView(JPArrayView& view) override;
 	virtual void releaseView(JPArrayView& view) override;
 	virtual const char* getBufferFormat() override;
-	virtual ssize_t getItemSize() override;
+	virtual Py_ssize_t getItemSize() override;
 	virtual void copyElements(JPJavaFrame &frame,
 			jarray a, jsize start, jsize len,
 			void* memory, int offset) override;
@@ -94,4 +94,4 @@ class JPFloatType : public JPPrimitiveType
 
 } ;
 
-#endif // _JP_FLOAT_TYPE_H_
\ No newline at end of file
+#endif // _JP_FLOAT_TYPE_H_
diff --git a/native/common/include/jp_inttype.h b/native/common/include/jp_inttype.h
index 2d7876a83..590be95bb 100755
--- a/native/common/include/jp_inttype.h
+++ b/native/common/include/jp_inttype.h
@@ -89,7 +89,7 @@ class JPIntType : public JPPrimitiveType
 	virtual void getView(JPArrayView& view) override;
 	virtual void releaseView(JPArrayView& view) override;
 	virtual const char* getBufferFormat() override;
-	virtual ssize_t getItemSize() override;
+	virtual Py_ssize_t getItemSize() override;
 	virtual void copyElements(JPJavaFrame &frame,
 			jarray a, jsize start, jsize len,
 			void* memory, int offset) override;
@@ -99,4 +99,4 @@ class JPIntType : public JPPrimitiveType
 
 } ;
 
-#endif // _JP_INT_TYPE_H_
\ No newline at end of file
+#endif // _JP_INT_TYPE_H_
diff --git a/native/common/include/jp_longtype.h b/native/common/include/jp_longtype.h
index 6319ac60d..0c2be13a0 100755
--- a/native/common/include/jp_longtype.h
+++ b/native/common/include/jp_longtype.h
@@ -88,7 +88,7 @@ class JPLongType : public JPPrimitiveType
 	virtual void getView(JPArrayView& view) override;
 	virtual void releaseView(JPArrayView& view) override;
 	virtual const char* getBufferFormat() override;
-	virtual ssize_t getItemSize() override;
+	virtual Py_ssize_t getItemSize() override;
 	virtual void copyElements(JPJavaFrame &frame,
 			jarray a, jsize start, jsize len,
 			void* memory, int offset) override;
@@ -98,4 +98,4 @@ class JPLongType : public JPPrimitiveType
 
 } ;
 
-#endif // _JP_LONG_TYPE_H_
\ No newline at end of file
+#endif // _JP_LONG_TYPE_H_
diff --git a/native/common/include/jp_primitivetype.h b/native/common/include/jp_primitivetype.h
index 654251fed..281bebf71 100644
--- a/native/common/include/jp_primitivetype.h
+++ b/native/common/include/jp_primitivetype.h
@@ -41,7 +41,7 @@ class JPPrimitiveType : public JPClass
 	virtual void getView(JPArrayView& view) = 0;
 	virtual void releaseView(JPArrayView& view) = 0;
 	virtual const char* getBufferFormat() = 0;
-	virtual ssize_t getItemSize() = 0;
+	virtual Py_ssize_t getItemSize() = 0;
 	virtual void copyElements(JPJavaFrame &frame,
 			jarray a, jsize start, jsize len,
 			void* memory, int offset) = 0;
@@ -53,4 +53,4 @@ class JPPrimitiveType : public JPClass
 	PyObject *convertLong(PyTypeObject* wrapper, PyLongObject* tmp);
 } ;
 
-#endif
\ No newline at end of file
+#endif
diff --git a/native/common/include/jp_shorttype.h b/native/common/include/jp_shorttype.h
index ab9448a34..5a919cb1a 100755
--- a/native/common/include/jp_shorttype.h
+++ b/native/common/include/jp_shorttype.h
@@ -88,7 +88,7 @@ class JPShortType : public JPPrimitiveType
 	virtual void getView(JPArrayView& view) override;
 	virtual void releaseView(JPArrayView& view) override;
 	virtual const char* getBufferFormat() override;
-	virtual ssize_t getItemSize() override;
+	virtual Py_ssize_t getItemSize() override;
 	virtual void copyElements(JPJavaFrame &frame,
 			jarray a, jsize start, jsize len,
 			void* memory, int offset) override;
@@ -98,4 +98,4 @@ class JPShortType : public JPPrimitiveType
 
 } ;
 
-#endif // _JP_SHORT_TYPE_H_
\ No newline at end of file
+#endif // _JP_SHORT_TYPE_H_
diff --git a/native/common/include/jp_typemanager.h b/native/common/include/jp_typemanager.h
index 5cec58d4c..6038d86ee 100644
--- a/native/common/include/jp_typemanager.h
+++ b/native/common/include/jp_typemanager.h
@@ -41,6 +41,7 @@ class JPTypeManager
 	JPClass* findClassForObject(jobject obj);
 	void populateMethod(void* method, jobject obj);
 	void populateMembers(JPClass* cls);
+    int interfaceParameterCount(JPClass* cls);
 
 private:
 	JPContext* m_Context;
@@ -50,6 +51,7 @@ class JPTypeManager
 	jmethodID m_FindClassForObject;
 	jmethodID m_PopulateMethod;
 	jmethodID m_PopulateMembers;
+    jmethodID m_InterfaceParameterCount;
 } ;
 
 #endif // _JPCLASS_H_
\ No newline at end of file
diff --git a/native/common/include/jp_voidtype.h b/native/common/include/jp_voidtype.h
index 98ed741cc..039119798 100755
--- a/native/common/include/jp_voidtype.h
+++ b/native/common/include/jp_voidtype.h
@@ -54,7 +54,7 @@ class JPVoidType : public JPPrimitiveType
 	virtual void getView(JPArrayView& view) override;
 	virtual void releaseView(JPArrayView& view) override;
 	virtual const char* getBufferFormat() override;
-	virtual ssize_t getItemSize() override;
+	virtual Py_ssize_t getItemSize() override;
 	virtual void copyElements(JPJavaFrame &frame,
 			jarray a, jsize start, jsize len,
 			void* memory, int offset) override;
@@ -62,4 +62,4 @@ class JPVoidType : public JPPrimitiveType
 			JPPyBuffer& view, int subs, int base, jobject dims) override;
 } ;
 
-#endif // _JP_VOID_TYPE_H_
\ No newline at end of file
+#endif // _JP_VOID_TYPE_H_
diff --git a/native/common/jp_array.cpp b/native/common/jp_array.cpp
index 8b62444c2..2adf57e90 100644
--- a/native/common/jp_array.cpp
+++ b/native/common/jp_array.cpp
@@ -178,8 +178,8 @@ JPArrayView::JPArrayView(JPArray* array, jobject collection)
 	// Second element is the shape of the array from which we compute the
 	// memory size, the shape, and strides
 	int dims;
-	ssize_t itemsize;
-	ssize_t sz;
+	Py_ssize_t itemsize;
+	Py_ssize_t sz;
 	{
 		JPPrimitiveArrayAccessor<jintArray, jint*> accessor(frame, (jintArray) item1,
 				&JPJavaFrame::GetIntArrayElements, &JPJavaFrame::ReleaseIntArrayElements);
diff --git a/native/common/jp_arrayclass.cpp b/native/common/jp_arrayclass.cpp
index afc557e57..c719af0e6 100644
--- a/native/common/jp_arrayclass.cpp
+++ b/native/common/jp_arrayclass.cpp
@@ -43,6 +43,7 @@ JPMatch::Type JPArrayClass::findJavaConversion(JPMatch &match)
 			|| charArrayConversion->matches(this, match)
 			|| byteArrayConversion->matches(this, match)
 			|| sequenceConversion->matches(this, match)
+			|| hintsConversion->matches(this, match)
 			)
 		return match.type;
 	JP_TRACE("None");
@@ -57,6 +58,7 @@ void JPArrayClass::getConversionInfo(JPConversionInfo &info)
 	charArrayConversion->getInfo(this, info);
 	byteArrayConversion->getInfo(this, info);
 	sequenceConversion->getInfo(this, info);
+	hintsConversion->getInfo(this, info);
 	PyList_Append(info.ret, PyJPClass_create(frame, this).get());
 }
 
diff --git a/native/common/jp_booleantype.cpp b/native/common/jp_booleantype.cpp
index 07226e578..25a6317ce 100644
--- a/native/common/jp_booleantype.cpp
+++ b/native/common/jp_booleantype.cpp
@@ -333,7 +333,7 @@ const char* JPBooleanType::getBufferFormat()
 	return "?";
 }
 
-ssize_t JPBooleanType::getItemSize()
+Py_ssize_t JPBooleanType::getItemSize()
 {
 	return sizeof (jboolean);
 }
diff --git a/native/common/jp_bytetype.cpp b/native/common/jp_bytetype.cpp
index 68a93b921..a81db8407 100644
--- a/native/common/jp_bytetype.cpp
+++ b/native/common/jp_bytetype.cpp
@@ -271,7 +271,7 @@ const char* JPByteType::getBufferFormat()
 	return "b";
 }
 
-ssize_t JPByteType::getItemSize()
+Py_ssize_t JPByteType::getItemSize()
 {
 	return sizeof (jbyte);
 }
diff --git a/native/common/jp_chartype.cpp b/native/common/jp_chartype.cpp
index ff7c48dfb..24094bea5 100644
--- a/native/common/jp_chartype.cpp
+++ b/native/common/jp_chartype.cpp
@@ -274,7 +274,7 @@ const char* JPCharType::getBufferFormat()
 	return "H";
 }
 
-ssize_t JPCharType::getItemSize()
+Py_ssize_t JPCharType::getItemSize()
 {
 	return sizeof (jchar);
 }
diff --git a/native/common/jp_class.cpp b/native/common/jp_class.cpp
index 5e220635b..3ff941aad 100644
--- a/native/common/jp_class.cpp
+++ b/native/common/jp_class.cpp
@@ -424,7 +424,6 @@ void JPClass::getConversionInfo(JPConversionInfo &info)
 	JP_TRACE_OUT;
 }
 
-
 //</editor-fold>
 //<editor-fold desc="hierarchy" defaultstate="collapsed">
 
diff --git a/native/common/jp_classhints.cpp b/native/common/jp_classhints.cpp
index 50c663640..26494102f 100644
--- a/native/common/jp_classhints.cpp
+++ b/native/common/jp_classhints.cpp
@@ -469,6 +469,11 @@ class JPConversionBuffer : public JPConversion
 		// If it is a buffer we only need to test the first item in the list
 		JPPySequence seq = JPPySequence::use(match.object);
 		jlong length = seq.size();
+		if (length == -1 && PyErr_Occurred())
+		{
+			PyErr_Clear();
+			return match.type = JPMatch::_none;
+		}
 		match.type = JPMatch::_implicit;
 		if (length > 0)
 		{
@@ -491,6 +496,7 @@ class JPConversionBuffer : public JPConversion
 
 	virtual jvalue convert(JPMatch &match) override
 	{
+		JP_TRACE_IN("JPConversionBuffer::convert");
 		JPJavaFrame frame(*match.frame);
 		jvalue res;
 		JPArrayClass *acls = (JPArrayClass *) match.closure;
@@ -500,6 +506,7 @@ class JPConversionBuffer : public JPConversion
 		ccls->setArrayRange(frame, array, 0, length, 1, match.object);
 		res.l = frame.keep(array);
 		return res;
+		JP_TRACE_OUT;
 	}
 }  _bufferConversion;
 
@@ -516,6 +523,11 @@ class JPConversionSequence : public JPConversion
 		JPClass *componentType = acls->getComponentType();
 		JPPySequence seq = JPPySequence::use(match.object);
 		jlong length = seq.size();
+		if (length==-1 && PyErr_Occurred())
+		{
+			PyErr_Clear();
+			return match.type = JPMatch::_none;
+		}
 		match.type = JPMatch::_implicit;
 		for (jlong i = 0; i < length && match.type > JPMatch::_none; i++)
 		{
diff --git a/native/common/jp_classtype.cpp b/native/common/jp_classtype.cpp
index b79f573a7..f02c30e25 100644
--- a/native/common/jp_classtype.cpp
+++ b/native/common/jp_classtype.cpp
@@ -36,11 +36,10 @@ JPClassType::~JPClassType()
 JPMatch::Type JPClassType::findJavaConversion(JPMatch& match)
 {
 	JP_TRACE_IN("JPClass::findJavaConversion");
-	if (nullConversion->matches(this, match) != JPMatch::_none)
-		return match.type;
-	if (objectConversion->matches(this, match) != JPMatch::_none)
-		return match.type;
-	if (classConversion->matches(this, match) != JPMatch::_none)
+	if (nullConversion->matches(this, match)
+			|| objectConversion->matches(this, match)
+			|| classConversion->matches(this, match)
+			|| hintsConversion->matches(this, match))
 		return match.type;
 	return match.type = JPMatch::_none;
 	JP_TRACE_OUT;
@@ -52,5 +51,6 @@ void JPClassType::getConversionInfo(JPConversionInfo &info)
 	nullConversion->getInfo(this, info);
 	objectConversion->getInfo(this, info);
 	classConversion->getInfo(this, info);
+	hintsConversion->getInfo(this, info);
 	PyList_Append(info.ret, PyJPClass_create(frame, this).get());
-}
\ No newline at end of file
+}
diff --git a/native/common/jp_context.cpp b/native/common/jp_context.cpp
index 40a017216..7e1184a06 100644
--- a/native/common/jp_context.cpp
+++ b/native/common/jp_context.cpp
@@ -282,14 +282,10 @@ void JPContext::initializeResources(JNIEnv* env, bool interrupt)
 
 	if (!m_Embedded)
 	{
-		JPPyObject import = JPPyObject::call(PyImport_AddModule("importlib.util"));
+		JPPyObject import = JPPyObject::use(PyImport_AddModule("importlib.util"));
 		JPPyObject jpype = JPPyObject::call(PyObject_CallMethod(import.get(), "find_spec", "s", "_jpype"));
 		JPPyObject origin = JPPyObject::call(PyObject_GetAttrString(jpype.get(), "origin"));
 		val[2].l = frame.fromStringUTF8(JPPyString::asStringUTF8(origin.get()));
-		import.incref();  // The documentation specifies that PyImport_AddModule must return a 
-	                  // new reference, but that is not happening in Python 3.10
-	                  // so we are triggering a gc assertion failure.  To prevent
-	                  // the failure manually up the reference counter here.
 	}
 	m_JavaContext = JPObjectRef(frame, frame.CallStaticObjectMethodA(contextClass, startMethod, val));
 
diff --git a/native/common/jp_convert.cpp b/native/common/jp_convert.cpp
index 29a2a6d78..04e5f6e4d 100644
--- a/native/common/jp_convert.cpp
+++ b/native/common/jp_convert.cpp
@@ -80,6 +80,49 @@ class Convert
 	}
 
 } ;
+
+template <jvalue func(void *c) >
+class Reverse
+{
+public:
+
+	static jvalue call2(void* c)
+	{
+		char r[2];
+		char* c2 = (char*)c;
+		r[0]=c2[1];
+		r[1]=c2[0];
+		return func(r);
+	}
+
+	static jvalue call4(void* c)
+	{
+		char r[4];
+		char* c2 = (char*)c;
+		r[0]=c2[3];
+		r[1]=c2[2];
+		r[2]=c2[1];
+		r[3]=c2[0];
+		return func(r);
+	}
+
+	static jvalue call8(void* c)
+	{
+		char r[8];
+		char* c2 = (char*)c;
+		r[0]=c2[7];
+		r[1]=c2[6];
+		r[2]=c2[5];
+		r[3]=c2[4];
+		r[4]=c2[3];
+		r[5]=c2[2];
+		r[6]=c2[1];
+		r[7]=c2[0];
+		return func(r);
+	}
+} ;
+
+
 } // namespace
 
 jconverter getConverter(const char* from, int itemsize, const char* to)
@@ -87,11 +130,37 @@ jconverter getConverter(const char* from, int itemsize, const char* to)
 	// If not specified then the type is bytes
 	if (from == NULL)
 		from = "B";
+
+	// Skip specifiers
+	bool reverse = false;
+	unsigned int x = 1;
+	bool little = *((char*)&x)==1;
+	switch (from[0])
+	{
+		case '!':
+		case '>':
+			if (little)
+				reverse = true;
+			from++;
+			break;
+		case '<':
+			if (!little)
+				reverse = true;
+			from++;
+			break;
+		case '@':
+		case '=':
+			from++;
+		default:
+			break;
+	}
+
 	// Standard size for 'l' is 4 in docs, but numpy uses format 'l' for long long
 	if (itemsize == 8 && from[0] == 'l')
 		from = "q";
 	if (itemsize == 8 && from[0] == 'L')
 		from = "Q";
+
 	switch (from[0])
 	{
 		case '?':
@@ -108,7 +177,7 @@ jconverter getConverter(const char* from, int itemsize, const char* to)
 				case 'f': return &Convert<int8_t>::toF;
 				case 'd': return &Convert<int8_t>::toD;
 			}
-			return 0;
+			break;
 		case 'B':
 			switch (to[0])
 			{
@@ -121,9 +190,20 @@ jconverter getConverter(const char* from, int itemsize, const char* to)
 				case 'f': return &Convert<uint8_t>::toF;
 				case 'd': return &Convert<uint8_t>::toD;
 			}
-			return 0;
+			break;
 		case 'h':
-			switch (to[0])
+			if (reverse) switch (to[0])
+			{
+				case 'z': return &Reverse<Convert<int16_t>::toZ>::call2;
+				case 'b': return &Reverse<Convert<int16_t>::toB>::call2;
+				case 'c': return &Reverse<Convert<int16_t>::toC>::call2;
+				case 's': return &Reverse<Convert<int16_t>::toS>::call2;
+				case 'i': return &Reverse<Convert<int16_t>::toI>::call2;
+				case 'j': return &Reverse<Convert<int16_t>::toJ>::call2;
+				case 'f': return &Reverse<Convert<int16_t>::toF>::call2;
+				case 'd': return &Reverse<Convert<int16_t>::toD>::call2;
+			}
+			else switch (to[0])
 			{
 				case 'z': return &Convert<int16_t>::toZ;
 				case 'b': return &Convert<int16_t>::toB;
@@ -134,9 +214,20 @@ jconverter getConverter(const char* from, int itemsize, const char* to)
 				case 'f': return &Convert<int16_t>::toF;
 				case 'd': return &Convert<int16_t>::toD;
 			}
-			return 0;
+			break;
 		case 'H':
-			switch (to[0])
+			if (reverse) switch (to[0])
+			{
+				case 'z': return &Reverse<Convert<uint16_t>::toZ>::call2;
+				case 'b': return &Reverse<Convert<uint16_t>::toB>::call2;
+				case 'c': return &Reverse<Convert<uint16_t>::toC>::call2;
+				case 's': return &Reverse<Convert<uint16_t>::toS>::call2;
+				case 'i': return &Reverse<Convert<uint16_t>::toI>::call2;
+				case 'j': return &Reverse<Convert<uint16_t>::toJ>::call2;
+				case 'f': return &Reverse<Convert<uint16_t>::toF>::call2;
+				case 'd': return &Reverse<Convert<uint16_t>::toD>::call2;
+			}
+			else switch (to[0])
 			{
 				case 'z': return &Convert<uint16_t>::toZ;
 				case 'b': return &Convert<uint16_t>::toB;
@@ -147,10 +238,21 @@ jconverter getConverter(const char* from, int itemsize, const char* to)
 				case 'f': return &Convert<uint16_t>::toF;
 				case 'd': return &Convert<uint16_t>::toD;
 			}
-			return 0;
+			break;
 		case 'i':
 		case 'l':
-			switch (to[0])
+			if (reverse) switch (to[0])
+			{
+				case 'z': return &Reverse<Convert<int32_t>::toZ>::call4;
+				case 'b': return &Reverse<Convert<int32_t>::toB>::call4;
+				case 'c': return &Reverse<Convert<int32_t>::toC>::call4;
+				case 's': return &Reverse<Convert<int32_t>::toS>::call4;
+				case 'i': return &Reverse<Convert<int32_t>::toI>::call4;
+				case 'j': return &Reverse<Convert<int32_t>::toJ>::call4;
+				case 'f': return &Reverse<Convert<int32_t>::toF>::call4;
+				case 'd': return &Reverse<Convert<int32_t>::toD>::call4;
+			}
+			else switch (to[0])
 			{
 				case 'z': return &Convert<int32_t>::toZ;
 				case 'b': return &Convert<int32_t>::toB;
@@ -161,10 +263,21 @@ jconverter getConverter(const char* from, int itemsize, const char* to)
 				case 'f': return &Convert<int32_t>::toF;
 				case 'd': return &Convert<int32_t>::toD;
 			}
-			return 0;
+			break;
 		case 'I':
 		case 'L':
-			switch (to[0])
+			if (reverse) switch (to[0])
+			{
+				case 'z': return &Reverse<Convert<uint32_t>::toZ>::call4;
+				case 'b': return &Reverse<Convert<uint32_t>::toB>::call4;
+				case 'c': return &Reverse<Convert<uint32_t>::toC>::call4;
+				case 's': return &Reverse<Convert<uint32_t>::toS>::call4;
+				case 'i': return &Reverse<Convert<uint32_t>::toI>::call4;
+				case 'j': return &Reverse<Convert<uint32_t>::toJ>::call4;
+				case 'f': return &Reverse<Convert<uint32_t>::toF>::call4;
+				case 'd': return &Reverse<Convert<uint32_t>::toD>::call4;
+			}
+			else switch (to[0])
 			{
 				case 'z': return &Convert<uint32_t>::toZ;
 				case 'b': return &Convert<uint32_t>::toB;
@@ -175,9 +288,20 @@ jconverter getConverter(const char* from, int itemsize, const char* to)
 				case 'f': return &Convert<uint32_t>::toF;
 				case 'd': return &Convert<uint32_t>::toD;
 			}
-			return 0;
+			break;
 		case 'q':
-			switch (to[0])
+			if (reverse) switch (to[0])
+			{
+				case 'z': return &Reverse<Convert<int64_t>::toZ>::call8;
+				case 'b': return &Reverse<Convert<int64_t>::toB>::call8;
+				case 'c': return &Reverse<Convert<int64_t>::toC>::call8;
+				case 's': return &Reverse<Convert<int64_t>::toS>::call8;
+				case 'i': return &Reverse<Convert<int64_t>::toI>::call8;
+				case 'j': return &Reverse<Convert<int64_t>::toJ>::call8;
+				case 'f': return &Reverse<Convert<int64_t>::toF>::call8;
+				case 'd': return &Reverse<Convert<int64_t>::toD>::call8;
+			}
+			else switch (to[0])
 			{
 				case 'z': return &Convert<int64_t>::toZ;
 				case 'b': return &Convert<int64_t>::toB;
@@ -188,9 +312,20 @@ jconverter getConverter(const char* from, int itemsize, const char* to)
 				case 'f': return &Convert<int64_t>::toF;
 				case 'd': return &Convert<int64_t>::toD;
 			}
-			return 0;
+			break;
 		case 'Q':
-			switch (to[0])
+			if (reverse) switch (to[0])
+			{
+				case 'z': return &Reverse<Convert<uint64_t>::toZ>::call8;
+				case 'b': return &Reverse<Convert<uint64_t>::toB>::call8;
+				case 'c': return &Reverse<Convert<uint64_t>::toC>::call8;
+				case 's': return &Reverse<Convert<uint64_t>::toS>::call8;
+				case 'i': return &Reverse<Convert<uint64_t>::toI>::call8;
+				case 'j': return &Reverse<Convert<uint64_t>::toJ>::call8;
+				case 'f': return &Reverse<Convert<uint64_t>::toF>::call8;
+				case 'd': return &Reverse<Convert<uint64_t>::toD>::call8;
+			}
+			else switch (to[0])
 			{
 				case 'z': return &Convert<uint64_t>::toZ;
 				case 'b': return &Convert<uint64_t>::toB;
@@ -201,9 +336,20 @@ jconverter getConverter(const char* from, int itemsize, const char* to)
 				case 'f': return &Convert<uint64_t>::toF;
 				case 'd': return &Convert<uint64_t>::toD;
 			}
-			return 0;
+			break;
 		case 'f':
-			switch (to[0])
+			if (reverse) switch (to[0])
+			{
+				case 'z': return &Reverse<Convert<float>::toZ>::call4;
+				case 'b': return &Reverse<Convert<float>::toB>::call4;
+				case 'c': return &Reverse<Convert<float>::toC>::call4;
+				case 's': return &Reverse<Convert<float>::toS>::call4;
+				case 'i': return &Reverse<Convert<float>::toI>::call4;
+				case 'j': return &Reverse<Convert<float>::toJ>::call4;
+				case 'f': return &Reverse<Convert<float>::toF>::call4;
+				case 'd': return &Reverse<Convert<float>::toD>::call4;
+			}
+			else switch (to[0])
 			{
 				case 'z': return &Convert<float>::toZ;
 				case 'b': return &Convert<float>::toB;
@@ -214,9 +360,20 @@ jconverter getConverter(const char* from, int itemsize, const char* to)
 				case 'f': return &Convert<float>::toF;
 				case 'd': return &Convert<float>::toD;
 			}
-			return 0;
+			break;
 		case 'd':
-			switch (to[0])
+			if (reverse) switch (to[0])
+			{
+				case 'z': return &Reverse<Convert<double>::toZ>::call8;
+				case 'b': return &Reverse<Convert<double>::toB>::call8;
+				case 'c': return &Reverse<Convert<double>::toC>::call8;
+				case 's': return &Reverse<Convert<double>::toS>::call8;
+				case 'i': return &Reverse<Convert<double>::toI>::call8;
+				case 'j': return &Reverse<Convert<double>::toJ>::call8;
+				case 'f': return &Reverse<Convert<double>::toF>::call8;
+				case 'd': return &Reverse<Convert<double>::toD>::call8;
+			}
+			else switch (to[0])
 			{
 				case 'z': return &Convert<double>::toZ;
 				case 'b': return &Convert<double>::toB;
@@ -227,10 +384,57 @@ jconverter getConverter(const char* from, int itemsize, const char* to)
 				case 'f': return &Convert<double>::toF;
 				case 'd': return &Convert<double>::toD;
 			}
-			return 0;
+			break;
 		case 'n':
+			if (reverse) switch (to[0])
+			{
+				case 'z': return &Reverse<Convert<Py_ssize_t>::toZ>::call8;
+				case 'b': return &Reverse<Convert<Py_ssize_t>::toB>::call8;
+				case 'c': return &Reverse<Convert<Py_ssize_t>::toC>::call8;
+				case 's': return &Reverse<Convert<Py_ssize_t>::toS>::call8;
+				case 'i': return &Reverse<Convert<Py_ssize_t>::toI>::call8;
+				case 'j': return &Reverse<Convert<Py_ssize_t>::toJ>::call8;
+				case 'f': return &Reverse<Convert<Py_ssize_t>::toF>::call8;
+				case 'd': return &Reverse<Convert<Py_ssize_t>::toD>::call8;
+			}
+			else switch (to[0])
+			{
+				case 'z': return &Convert<Py_ssize_t>::toZ;
+				case 'b': return &Convert<Py_ssize_t>::toB;
+				case 'c': return &Convert<Py_ssize_t>::toC;
+				case 's': return &Convert<Py_ssize_t>::toS;
+				case 'i': return &Convert<Py_ssize_t>::toI;
+				case 'j': return &Convert<Py_ssize_t>::toJ;
+				case 'f': return &Convert<Py_ssize_t>::toF;
+				case 'd': return &Convert<Py_ssize_t>::toD;
+			}
+			break;
 		case 'N':
-		case 'P':
-		default: return 0;
+			if (reverse) switch (to[0])
+			{
+				case 'z': return &Reverse<Convert<size_t>::toZ>::call8;
+				case 'b': return &Reverse<Convert<size_t>::toB>::call8;
+				case 'c': return &Reverse<Convert<size_t>::toC>::call8;
+				case 's': return &Reverse<Convert<size_t>::toS>::call8;
+				case 'i': return &Reverse<Convert<size_t>::toI>::call8;
+				case 'j': return &Reverse<Convert<size_t>::toJ>::call8;
+				case 'f': return &Reverse<Convert<size_t>::toF>::call8;
+				case 'd': return &Reverse<Convert<size_t>::toD>::call8;
+			}
+			else switch (to[0])
+			{
+				case 'z': return &Convert<size_t>::toZ;
+				case 'b': return &Convert<size_t>::toB;
+				case 'c': return &Convert<size_t>::toC;
+				case 's': return &Convert<size_t>::toS;
+				case 'i': return &Convert<size_t>::toI;
+				case 'j': return &Convert<size_t>::toJ;
+				case 'f': return &Convert<size_t>::toF;
+				case 'd': return &Convert<size_t>::toD;
+			}
+			break;
+		default: break;
 	}
+	PyErr_Format(PyExc_ValueError, "Unable to handle buffer type '%s'", from);
+	JP_RAISE_PYTHON();
 }
diff --git a/native/common/jp_doubletype.cpp b/native/common/jp_doubletype.cpp
index 7a5f4a81b..ab7c0643c 100644
--- a/native/common/jp_doubletype.cpp
+++ b/native/common/jp_doubletype.cpp
@@ -312,7 +312,7 @@ const char* JPDoubleType::getBufferFormat()
 	return "d";
 }
 
-ssize_t JPDoubleType::getItemSize()
+Py_ssize_t JPDoubleType::getItemSize()
 {
 	return sizeof (jdouble);
 }
diff --git a/native/common/jp_exception.cpp b/native/common/jp_exception.cpp
index eb8eb20ee..1baed06f2 100644
--- a/native/common/jp_exception.cpp
+++ b/native/common/jp_exception.cpp
@@ -286,6 +286,7 @@ void JPypeException::toPython()
 {
 	string mesg;
 	JP_TRACE_IN("JPypeException::toPython");
+	JP_TRACE("err", PyErr_Occurred());
 	try
 	{
 		// Check the signals before processing the exception
@@ -297,6 +298,11 @@ void JPypeException::toPython()
 		mesg = getMessage();
 		JP_TRACE(m_Error.l);
 		JP_TRACE(mesg.c_str());
+
+		// We already have a Python error on the stack.
+		if (PyErr_Occurred())
+			return;
+
 		if (m_Type == JPError::_java_error)
 		{
 			JP_TRACE("Java exception");
@@ -483,79 +489,51 @@ void JPypeException::toJava(JPContext *context)
 	JP_TRACE_OUT; // GCOVR_EXCL_LINE
 }
 
-PyTracebackObject *tb_create(
-		PyTracebackObject *last_traceback,
+PyObject *tb_create(
+		PyObject *last_traceback,
 		PyObject *dict,
 		const char* filename,
 		const char* funcname,
 		int linenum)
 {
 	// Create a code for this frame. (ref count is 1)
-	PyCodeObject *code = PyCode_NewEmpty(filename, funcname, linenum);
+	JPPyObject code = JPPyObject::accept((PyObject*)PyCode_NewEmpty(filename, funcname, linenum));
 
 	// If we don't get the code object there is no point
-	if (code == NULL)
+	if (code.get() == NULL)
 		return NULL;
 
-	// This is a bit of a kludge.  Python lacks a way to directly create
-	// a frame from a code object except when creating from the threadstate.
-	//
-	// In reviewing Python implementation, I find that the only element accessed
-	// in the thread state was the previous frame to link to.  Because frame 
-	// objects change a lot between different Python versions, trying to 
-	// replicate the actions of setting up a frame is difficult to keep portable.
-	//
-	// Python 3.10 introduces the additional requirement that the global
-	// dictionary supplied must have a __builtins__.  We can do this once
-	// when create the module.
-	//
-	// If instead we create a thread state and point the field it needs to the
-	// previous frame we create the frames using the defined API.  Much more 
-	// portable, but we have to create a big (uninitialized object) each time we
-	// want to pass in the previous frame.
-	PyThreadState state;
-	if (last_traceback != NULL)
-		state.frame = last_traceback->tb_frame;
-	else
-		state.frame = NULL;
-
 	// Create a frame for the traceback.
-	PyFrameObject *frame = PyFrame_New(&state, code, dict, NULL);
-	
-	// frame just borrows the reference rather than claiming it
-	// so we need to get rid of the extra reference here.
-	Py_DECREF(code);
-	
+	PyThreadState *state = PyThreadState_GET();
+	PyFrameObject *pframe = PyFrame_New(state, (PyCodeObject*) code.get(), dict, NULL);
+	JPPyObject frame = JPPyObject::accept((PyObject*)pframe);
+
 	// If we don't get the frame object there is no point
-	if (frame == NULL)
+	if (frame.get() == NULL)
 		return NULL;
 
 	// Create a traceback
-	PyTracebackObject *traceback = (PyTracebackObject*) 
-		        PyObject_GC_New(PyTracebackObject, &PyTraceBack_Type);
+#if PY_MINOR_VERSION<11
+	JPPyObject lasti = JPPyObject::claim(PyLong_FromLong(pframe->f_lasti));
+#else
+	JPPyObject lasti = JPPyObject::claim(PyLong_FromLong(PyFrame_GetLasti(pframe)));
+#endif
+	JPPyObject linenuma = JPPyObject::claim(PyLong_FromLong(linenum));
+	JPPyObject tuple = JPPyObject::call(PyTuple_Pack(4, Py_None, frame.get(), lasti.get(), linenuma.get()));
+	JPPyObject traceback = JPPyObject::accept(PyObject_Call((PyObject*) &PyTraceBack_Type, tuple.get(), NULL));
 
 	// We could fail in process
-	if (traceback == NULL)
+	if (traceback.get() == NULL)
 	{
-		Py_DECREF(frame);
 		return NULL;
 	}
 
-	// Set the fields
-	traceback->tb_frame = frame; // Steal the reference from frame
-	traceback->tb_lasti = frame->f_lasti;
-	traceback->tb_lineno = linenum;
-	Py_XINCREF(last_traceback);
-	traceback->tb_next = last_traceback;
-
-	// Allow GC on the object
-	PyObject_GC_Track(traceback);
-	return traceback;
+	return traceback.keep();
 }
 
 PyObject* PyTrace_FromJPStackTrace(JPStackTrace& trace)
 {
-	PyTracebackObject *last_traceback = NULL;
+	PyObject *last_traceback = NULL;
 	PyObject *dict = PyModule_GetDict(PyJPModule);
 	for (JPStackTrace::iterator iter = trace.begin(); iter != trace.end(); ++iter)
 	{
@@ -569,7 +547,7 @@ PyObject* PyTrace_FromJPStackTrace(JPStackTrace& trace)
 
 JPPyObject PyTrace_FromJavaException(JPJavaFrame& frame, jthrowable th, jthrowable prev)
 {
-	PyTracebackObject *last_traceback = NULL;
+	PyObject *last_traceback = NULL;
 	JPContext *context = frame.getContext();
 	jvalue args[2];
 	args[0].l = th;
diff --git a/native/common/jp_floattype.cpp b/native/common/jp_floattype.cpp
index a8d7ed690..317e88f17 100644
--- a/native/common/jp_floattype.cpp
+++ b/native/common/jp_floattype.cpp
@@ -295,7 +295,7 @@ const char* JPFloatType::getBufferFormat()
 	return "f";
 }
 
-ssize_t JPFloatType::getItemSize()
+Py_ssize_t JPFloatType::getItemSize()
 {
 	return sizeof (jfloat);
 }
diff --git a/native/common/jp_functional.cpp b/native/common/jp_functional.cpp
index f94dba927..47702256d 100644
--- a/native/common/jp_functional.cpp
+++ b/native/common/jp_functional.cpp
@@ -32,6 +32,7 @@ JPFunctional::~JPFunctional()
 {
 }
 
+
 class JPConversionFunctional : public JPConversion
 {
 public:
@@ -40,6 +41,56 @@ class JPConversionFunctional : public JPConversion
 	{
 		if (!PyCallable_Check(match.object))
 			return match.type = JPMatch::_none;
+
+		// def my_func(x, y=None) should be both a Function and a BiFunction
+		// i.e. the number of parameters accepted by the interface MUST
+		// 1. Be at most the maximum number of parameters accepted by the python function (parameter_count)
+		//    (Unless the function accept a variable number of arguments, then this restriction does not
+		//     apply).
+		// 2. Be at least the minumum number of parameters accepted by the python function
+		// (parameter_count - optional_parameter_count = number of required parameters).
+		// Notes:
+		// - keywords vargs does not remove restriction 1
+		// - keyword only arguments are not counted.
+		if (PyFunction_Check(match.object))
+		{
+			PyObject* func = match.object; 
+			PyCodeObject* code = (PyCodeObject*) PyFunction_GetCode(func); // borrowed
+			Py_ssize_t args = code->co_argcount;
+			bool is_varargs = ((code->co_flags&CO_VARARGS)==CO_VARARGS);
+			int optional = 0;
+			JPPyObject defaults = JPPyObject::accept(PyObject_GetAttrString(func, "__defaults__"));
+			if (!defaults.isNull() && defaults.get() != Py_None)
+				optional = PyTuple_Size(defaults.get());
+			const int jargs = cls->getContext()->getTypeManager()->interfaceParameterCount(cls);
+			// Too few arguments
+			if (!is_varargs && args < jargs)
+				return match.type = JPMatch::_none;
+			// Too many arguments
+			if (args - optional > jargs) 
+				return match.type = JPMatch::_none;
+		}
+		else if (PyMethod_Check(match.object))
+		{
+			PyObject* func = PyMethod_Function(match.object); // borrowed
+			PyCodeObject* code = (PyCodeObject*) PyFunction_GetCode(func); // borrowed
+			Py_ssize_t args = code->co_argcount;
+			bool is_varargs = ((code->co_flags&CO_VARARGS)==CO_VARARGS);
+			int optional = 0;
+			JPPyObject defaults = JPPyObject::accept(PyObject_GetAttrString(func, "__defaults__"));
+			if (!defaults.isNull() && defaults.get() != Py_None)
+				optional = PyTuple_Size(defaults.get());
+			const int jargs = cls->getContext()->getTypeManager()->interfaceParameterCount(cls);
+			// Bound self argument removes one argument
+			if ((PyMethod_Self(match.object))!=NULL) // borrowed
+				args--;
+			// Too few arguments
+			if (!is_varargs && args < jargs)
+				return match.type = JPMatch::_none;
+			// Too many arguments
+			if (args - optional > jargs) 
+				return match.type = JPMatch::_none;
+		}
 		match.conversion = this;
 		match.closure = cls;
 		return match.type = JPMatch::_implicit;
diff --git a/native/common/jp_gc.cpp b/native/common/jp_gc.cpp
index 968a1da9b..8b711f997 100644
--- a/native/common/jp_gc.cpp
+++ b/native/common/jp_gc.cpp
@@ -234,9 +234,9 @@ void JPGarbageCollection::onEnd()
 		}
 
 		// Predict if we will cross the limit soon.
-		ssize_t pred = current + 2 * (current - last);
+		Py_ssize_t pred = current + 2 * (current - last);
 		last = current;
-		if ((ssize_t) pred > (ssize_t) limit)
+		if ((Py_ssize_t) pred > (Py_ssize_t) limit)
 			run_gc = 2;
 
 		//		printf("consider gc %d (%ld, %ld, %ld, %ld) %ld\n", run_gc,
diff --git a/native/common/jp_inttype.cpp b/native/common/jp_inttype.cpp
index 07d4bcdcd..77defb97d 100644
--- a/native/common/jp_inttype.cpp
+++ b/native/common/jp_inttype.cpp
@@ -275,7 +275,7 @@ void JPIntType::getView(JPArrayView& view)
 	view.m_IsCopy = false;
 	view.m_Memory = (void*) frame.GetIntArrayElements(
 			(jintArray) view.m_Array->getJava(), &view.m_IsCopy);
-	view.m_Buffer.format = "i";
+	view.m_Buffer.format = "=i";
 	view.m_Buffer.itemsize = sizeof (jint);
 }
 
@@ -295,10 +295,10 @@ void JPIntType::releaseView(JPArrayView& view)
 
 const char* JPIntType::getBufferFormat()
 {
-	return "i";
+	return "=i";
 }
 
-ssize_t JPIntType::getItemSize()
+Py_ssize_t JPIntType::getItemSize()
 {
 	return sizeof (jfloat);
 }
diff --git a/native/common/jp_longtype.cpp b/native/common/jp_longtype.cpp
index d5cb995b7..dc91feae1 100644
--- a/native/common/jp_longtype.cpp
+++ b/native/common/jp_longtype.cpp
@@ -274,7 +274,7 @@ void JPLongType::getView(JPArrayView& view)
 	JPJavaFrame frame = JPJavaFrame::outer(view.getContext());
 	view.m_Memory = (void*) frame.GetLongArrayElements(
 			(jlongArray) view.m_Array->getJava(), &view.m_IsCopy);
-	view.m_Buffer.format = "q";
+	view.m_Buffer.format = "=q";
 	view.m_Buffer.itemsize = sizeof (jlong);
 }
 
@@ -294,10 +294,10 @@ void JPLongType::releaseView(JPArrayView& view)
 
 const char* JPLongType::getBufferFormat()
 {
-	return "q";
+	return "=q";
 }
 
-ssize_t JPLongType::getItemSize()
+Py_ssize_t JPLongType::getItemSize()
 {
 	return sizeof (jlong);
 }
diff --git a/native/common/jp_methoddispatch.cpp b/native/common/jp_methoddispatch.cpp
index 3778dff4a..d1c5af5e8 100644
--- a/native/common/jp_methoddispatch.cpp
+++ b/native/common/jp_methoddispatch.cpp
@@ -61,6 +61,9 @@ bool JPMethodDispatch::findOverload(JPJavaFrame& frame, JPMethodMatch &bestMatch
 		// Anything better than explicit constitutes a hit on the cache
 		if (bestMatch.m_Type > JPMatch::_explicit)
 			return true;
+		else
+			// bad match so forget the overload.
+			bestMatch.m_Overload = 0;
 	}
 
 	// We need two copies of the match.  One to hold the best match we have
diff --git a/native/common/jp_shorttype.cpp b/native/common/jp_shorttype.cpp
index 372c8b829..12e60d979 100644
--- a/native/common/jp_shorttype.cpp
+++ b/native/common/jp_shorttype.cpp
@@ -294,7 +294,7 @@ const char* JPShortType::getBufferFormat()
 	return "h";
 }
 
-ssize_t JPShortType::getItemSize()
+Py_ssize_t JPShortType::getItemSize()
 {
 	return sizeof (jshort);
 }
diff --git a/native/common/jp_stringtype.cpp b/native/common/jp_stringtype.cpp
index 4ba607d6f..848c8a572 100644
--- a/native/common/jp_stringtype.cpp
+++ b/native/common/jp_stringtype.cpp
@@ -47,7 +47,7 @@ JPPyObject JPStringType::convertToPythonObject(JPJavaFrame& frame, jvalue val, b
 		if (context->getConvertStrings())
 		{
 			string str = frame.toStringUTF8((jstring) (val.l));
-			return JPPyObject::call(PyUnicode_FromString(str.c_str()));
+			return JPPyObject::call(PyUnicode_FromStringAndSize(str.c_str(), str.length()));
 		}
 	}
 
diff --git a/native/common/jp_typemanager.cpp b/native/common/jp_typemanager.cpp
index 91edaeb06..82ff13f60 100644
--- a/native/common/jp_typemanager.cpp
+++ b/native/common/jp_typemanager.cpp
@@ -27,6 +27,7 @@ JPTypeManager::JPTypeManager(JPJavaFrame& frame)
 	m_FindClassForObject = frame.GetMethodID(cls, "findClassForObject", "(Ljava/lang/Object;)J");
 	m_PopulateMethod = frame.GetMethodID(cls, "populateMethod", "(JLjava/lang/reflect/Executable;)V");
 	m_PopulateMembers = frame.GetMethodID(cls, "populateMembers", "(Ljava/lang/Class;)V");
+    m_InterfaceParameterCount = frame.GetMethodID(cls, "interfaceParameterCount", "(Ljava/lang/Class;)I");
 
 	// The object instance will be loaded later
 	JP_TRACE_OUT;
@@ -97,3 +98,13 @@ void JPTypeManager::populateMembers(JPClass* cls)
 	frame.CallVoidMethodA(m_JavaTypeManager.get(), m_PopulateMembers, val);
 	JP_TRACE_OUT;
 }
+
+int JPTypeManager::interfaceParameterCount(JPClass *cls)
+{
+	JP_TRACE_IN("JPTypeManager::interfaceParameterCount");
+	JPJavaFrame frame = JPJavaFrame::outer(m_Context);
+	jvalue val[1];
+	val[0].l = (jobject) cls->getJavaClass();
+	return frame.CallIntMethodA(m_JavaTypeManager.get(), m_InterfaceParameterCount, val);
+	JP_TRACE_OUT;
+}
diff --git a/native/common/jp_voidtype.cpp b/native/common/jp_voidtype.cpp
index 2ab201acb..fcbe9cff3 100644
--- a/native/common/jp_voidtype.cpp
+++ b/native/common/jp_voidtype.cpp
@@ -122,7 +122,7 @@ const char* JPVoidType::getBufferFormat()
 	return NULL;
 }
 
-ssize_t JPVoidType::getItemSize()
+Py_ssize_t JPVoidType::getItemSize()
 {
 	return 0;
 }
@@ -154,4 +154,4 @@ PyObject *JPVoidType::newMultiArray(JPJavaFrame &frame,
 	return NULL;
 }
 
-// GCOVR_EXCL_STOP
\ No newline at end of file
+// GCOVR_EXCL_STOP
diff --git a/native/java/org/jpype/JPypeContext.java b/native/java/org/jpype/JPypeContext.java
index d8886c3db..19dbf1d37 100644
--- a/native/java/org/jpype/JPypeContext.java
+++ b/native/java/org/jpype/JPypeContext.java
@@ -73,7 +73,7 @@
 public class JPypeContext
 {
 
-  public final String VERSION = "1.3.0";
+  public final String VERSION = "1.4.1";
 
   private static JPypeContext INSTANCE = new JPypeContext();
   // This is the C++ portion of the context.
diff --git a/native/java/org/jpype/javadoc/JavadocExtractor.java b/native/java/org/jpype/javadoc/JavadocExtractor.java
index 2fa0ce8cf..dca342c45 100644
--- a/native/java/org/jpype/javadoc/JavadocExtractor.java
+++ b/native/java/org/jpype/javadoc/JavadocExtractor.java
@@ -115,7 +115,11 @@ public static Javadoc extractDocument(Class cls, Document doc)
     {
       Javadoc documentation = new Javadoc();
       XPath xPath = XPathFactory.newInstance().newXPath();
-      Node description = toFragment((Node) xPath.compile("//div[@class='description']/ul/li").evaluate(doc, XPathConstants.NODE));
+      // Javadoc 8-13
+      Node n = (Node) xPath.compile("//div[@class='description']/ul/li").evaluate(doc, XPathConstants.NODE);
+      if (n == null)  // Javadoc 14+
+        n = (Node) xPath.compile("//section[@class='description']").evaluate(doc, XPathConstants.NODE);
+      Node description = toFragment(n);
       if (description != null)
       {
         documentation.descriptionNode = description;
diff --git a/native/java/org/jpype/manager/ClassDescriptor.java b/native/java/org/jpype/manager/ClassDescriptor.java
index 265853586..aefe1aff8 100644
--- a/native/java/org/jpype/manager/ClassDescriptor.java
+++ b/native/java/org/jpype/manager/ClassDescriptor.java
@@ -46,6 +46,7 @@ public class ClassDescriptor
   public int methodCounter = 0;
   public long[] fields;
   public long anonymous;
+  public int functional_interface_parameter_count = -1; // -1 = unset; -2 = not a functional interface
 
   ClassDescriptor(Class cls, long classPtr)
   {
diff --git a/native/java/org/jpype/manager/TypeManager.java b/native/java/org/jpype/manager/TypeManager.java
index a36f8d7de..3cdd9ea9d 100644
--- a/native/java/org/jpype/manager/TypeManager.java
+++ b/native/java/org/jpype/manager/TypeManager.java
@@ -300,6 +300,34 @@ public synchronized void populateMethod(long wrapper, Executable method)
     }
   }
 
+  /**
+   * Returns the number of arguments an interface only unimplemented method accept.
+   *
+   * @param interfaceClass The class of the interface
+   * @return the number of arguments the only unimplemented method of the interface accept.
+   */
+  public int interfaceParameterCount(Class<?> interfaceClass) {
+    ClassDescriptor classDescriptor = classMap.get(interfaceClass);
+    if (classDescriptor.functional_interface_parameter_count != -1) {
+      return classDescriptor.functional_interface_parameter_count;
+    }
+    int abstractMethodParameterCount = -1;
+    for (Method method : interfaceClass.getMethods()) {
+      if (Modifier.isAbstract(method.getModifiers())) {
+        if (abstractMethodParameterCount != -1) {
+          classDescriptor.functional_interface_parameter_count = -2;
+          return -2;
+        }
+        abstractMethodParameterCount = method.getParameterCount();
+      }
+    }
+    if (abstractMethodParameterCount == -1) {
+      abstractMethodParameterCount = -2;
+    }
+    classDescriptor.functional_interface_parameter_count = abstractMethodParameterCount;
+    return abstractMethodParameterCount;
+  }
+
   /**
    * Get a class for an object.
    *
diff --git a/native/python/include/jp_pythontypes.h b/native/python/include/jp_pythontypes.h
index 93e91d8e0..58575cd1e 100755
--- a/native/python/include/jp_pythontypes.h
+++ b/native/python/include/jp_pythontypes.h
@@ -281,7 +281,7 @@ class JPPyObjectVector
 		return m_Contents.size();
 	}
 
-	PyObject* operator[](ssize_t i)
+	PyObject* operator[](Py_ssize_t i)
 	{
 		return m_Contents[i].get();
 	}
diff --git a/native/python/jp_pythontypes.cpp b/native/python/jp_pythontypes.cpp
index 05485e73d..3bf4b4e37 100644
--- a/native/python/jp_pythontypes.cpp
+++ b/native/python/jp_pythontypes.cpp
@@ -244,7 +244,7 @@ jchar JPPyString::asCharUTF16(PyObject* pyobj)
 		Py_UCS4 value = PyUnicode_READ_CHAR(pyobj, 0);
 		if (value > 0xffff)
 		{
-			JP_RAISE(PyExc_ValueError, "Unable to pack 4 byte unicode into java char");
+			JP_RAISE(PyExc_ValueError, "Unable to pack 4 byte unicode into Java char");
 		}
 		return value;
 	}
@@ -268,12 +268,12 @@ jchar JPPyString::asCharUTF16(PyObject* pyobj)
 		Py_UCS4 value = PyUnicode_ReadChar(pyobj, 0);
 		if (value > 0xffff)
 		{
-			JP_RAISE(PyExc_ValueError, "Unable to pack 4 byte unicode into java char");
+			JP_RAISE(PyExc_ValueError, "Unable to pack 4 byte unicode into Java char");
 		}
 		return value;
 	}
 #endif
-	PyErr_Format(PyExc_TypeError, "Unable to convert '%s'  to char", Py_TYPE(pyobj)->tp_name);
+	PyErr_Format(PyExc_TypeError, "Unable to convert '%s' to Java char", Py_TYPE(pyobj)->tp_name);
 	JP_RAISE_PYTHON();
 }
 
diff --git a/native/python/pyjp_array.cpp b/native/python/pyjp_array.cpp
index 3a702e3f8..9dc53774c 100644
--- a/native/python/pyjp_array.cpp
+++ b/native/python/pyjp_array.cpp
@@ -160,16 +160,12 @@ static PyObject *PyJPArray_getItem(PyJPArray *self, PyObject *item)
 		Py_ssize_t start, stop, step, slicelength;
 		Py_ssize_t length = (Py_ssize_t) self->m_Array->getLength();
 
-#if PY_VERSION_HEX<0x03060100
-		if (PySlice_GetIndicesEx(item, length, &start, &stop, &step, &slicelength) < 0)
-			return NULL;
-#else
 		if (PySlice_Unpack(item, &start, &stop, &step) < 0)
 			return NULL;
 
 		slicelength = PySlice_AdjustIndices(length, &start, &stop, step);
-#endif
-		if (slicelength <= 0)
+
+        if (slicelength <= 0)
 		{
 			// This should point to a null array so we don't hold worthless
 			// memory, but this is a low priority
@@ -231,16 +227,12 @@ static int PyJPArray_assignSubscript(PyJPArray *self, PyObject *item, PyObject *
 		Py_ssize_t start, stop, step, slicelength;
 		Py_ssize_t length = (Py_ssize_t) self->m_Array->getLength();
 
-#if PY_VERSION_HEX<0x03060100
-		if (PySlice_GetIndicesEx(item, length, &start, &stop, &step, &slicelength) < 0)
-			return -1;
-#else
 		if (PySlice_Unpack(item, &start, &stop, &step) < 0)
 			return -1;
 
 		slicelength = PySlice_AdjustIndices(length, &start, &stop, step);
-#endif
-		if (slicelength <= 0)
+
+        if (slicelength <= 0)
 			return 0;
 
 		self->m_Array->setRange((jsize) start, (jsize) slicelength, (jsize) step,  value);
diff --git a/native/python/pyjp_char.cpp b/native/python/pyjp_char.cpp
index 0adfdf892..5b4ab19da 100644
--- a/native/python/pyjp_char.cpp
+++ b/native/python/pyjp_char.cpp
@@ -63,11 +63,17 @@ static int isNull(JPValue *javaSlot)
 	return 1;
 }
 
+static PyObject* notSupported()
+{
+	PyErr_SetString(PyExc_TypeError, "unsupported operation");
+	return 0;
+}
+
 static int assertNotNull(JPValue *javaSlot)
 {
 	if (!isNull(javaSlot))
 		return 0;
-	PyErr_SetString(PyExc_TypeError, "cast of null pointer");
+	PyErr_SetString(PyExc_TypeError, "jchar cast of null pointer");
 	return 1;
 }
 
@@ -295,158 +301,152 @@ static Py_ssize_t PyJPChar_len(PyJPChar *self)
 	JP_PY_CATCH(-1);  // GCOVR_EXCL_LINE
 }
 
-static  PyObject *PyJPChar_and(PyJPChar *self, PyObject *other)
+static PyObject *apply(PyObject *first, PyObject *second, PyObject* (*func)(PyObject*, PyObject*))
+{
+	JPValue *slot0 = PyJPValue_getJavaSlot(first);
+	JPValue *slot1 = PyJPValue_getJavaSlot(second);
+	if (slot0 != NULL && slot1 != NULL)
+	{	
+		if (assertNotNull(slot0))
+			return 0;
+		if (assertNotNull(slot1))
+			return 0;
+		JPPyObject v1 = JPPyObject::call(PyLong_FromLong(fromJPChar((PyJPChar*)first)));
+		JPPyObject v2 = JPPyObject::call(PyLong_FromLong(fromJPChar((PyJPChar*)second)));
+		return func(v1.get(), v2.get());
+	}
+	else if (slot0 != NULL)
+	{
+		if (assertNotNull(slot0))
+			return 0;
+		// Promote to int as per Java rules
+		JPPyObject v = JPPyObject::call(PyLong_FromLong(fromJPChar((PyJPChar*)first)));
+		return func(v.get(), second);
+	}
+	else if (slot1 != NULL)
+	{
+		if (assertNotNull(slot1))
+			return 0;
+		// Promote to int as per Java rules
+		JPPyObject v = JPPyObject::call(PyLong_FromLong(fromJPChar((PyJPChar*)second)));
+		return func(first, v.get());
+	}
+	return notSupported();
+}
+
+
+static  PyObject *PyJPChar_and(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_and");
 	PyJPModule_getContext();  // Check that JVM is running
-	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
-	if (assertNotNull(javaSlot))
-		return 0;
-	// Promote to int as per Java rules
-	JPPyObject v = JPPyObject::call(PyLong_FromLong(fromJPChar(self)));
-	return PyNumber_And(v.get(), other);
+	return apply(first, second, PyNumber_And);
 	JP_PY_CATCH(NULL);  // GCOVR_EXCL_LINE
 }
 
-static  PyObject *PyJPChar_or(PyJPChar *self, PyObject *other)
+static  PyObject *PyJPChar_or(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_or");
 	PyJPModule_getContext();  // Check that JVM is running
-	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
-	if (assertNotNull(javaSlot))
-		return 0;
-	// Promote to int as per Java rules
-	JPPyObject v = JPPyObject::call(PyLong_FromLong(fromJPChar(self)));
-	return PyNumber_Or(v.get(), other);
+	return apply(first, second, PyNumber_Or);
 	JP_PY_CATCH(NULL);  // GCOVR_EXCL_LINE
 }
 
-static  PyObject *PyJPChar_xor(PyJPChar *self, PyObject *other)
+static  PyObject *PyJPChar_xor(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_xor");
 	PyJPModule_getContext();  // Check that JVM is running
-	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
-	if (assertNotNull(javaSlot))
-		return 0;
-	// Promote to int as per Java rules
-	JPPyObject v = JPPyObject::call(PyLong_FromLong(fromJPChar(self)));
-	return PyNumber_Xor(v.get(), other);
+	return apply(first, second, PyNumber_Xor);
 	JP_PY_CATCH(NULL);  // GCOVR_EXCL_LINE
 }
 
-static  PyObject *PyJPChar_add(PyJPChar *self, PyObject *other)
+static  PyObject *PyJPChar_add(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_add");
 	PyJPModule_getContext();  // Check that JVM is running
-	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
-	if (assertNotNull(javaSlot))
-		return 0;
-	if (PyUnicode_Check(other))
-		return PyUnicode_Type.tp_as_number->nb_add((PyObject*) self, other);
+	JPValue *slot0 = PyJPValue_getJavaSlot(first);
+	JPValue *slot1 = PyJPValue_getJavaSlot(second);
+	if (slot1 != NULL && slot0 != NULL)
+	{	
+		if (assertNotNull(slot0))
+			return 0;
+		if (assertNotNull(slot1))
+			return 0;
+		JPPyObject v1 = JPPyObject::call(PyLong_FromLong(fromJPChar((PyJPChar*)first)));
+		JPPyObject v2 = JPPyObject::call(PyLong_FromLong(fromJPChar((PyJPChar*)second)));
+		return PyNumber_Add(v1.get(), v2.get());
+	}
+	else if (slot0 != NULL)
+	{
+		if (assertNotNull(slot0))
+			return 0;
+		if (PyUnicode_Check(second))
+			return PyUnicode_Concat(first, second);
 
-	// Promote to int as per Java rules
-	JPPyObject v = JPPyObject::call(PyLong_FromLong(fromJPChar(self)));
-	return PyNumber_Add(v.get(), other);
+		// Promote to int as per Java rules
+		JPPyObject v = JPPyObject::call(PyLong_FromLong(fromJPChar((PyJPChar*)first)));
+		return PyNumber_Add(v.get(), second);
+	}
+	else if (slot1 != NULL)
+	{
+		if (assertNotNull(slot1))
+			return 0;
+		if (PyUnicode_Check(first))
+			return PyUnicode_Concat(first, second);
+
+		// Promote to int as per Java rules
+		JPPyObject v = JPPyObject::call(PyLong_FromLong(fromJPChar((PyJPChar*)second)));
+		return PyNumber_Add(first, v.get());
+	}
+	return notSupported();
 	JP_PY_CATCH(NULL);  // GCOVR_EXCL_LINE
 }
 
-static  PyObject *PyJPChar_subtract(PyJPChar *self, PyObject *other)
+
+static  PyObject *PyJPChar_subtract(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_subtract");
 	PyJPModule_getContext();  // Check that JVM is running
-	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
-	if (assertNotNull(javaSlot))
-		return 0;
-
-	// Promote to int as per Java rules
-	JPPyObject v = JPPyObject::call(PyLong_FromLong(fromJPChar(self)));
-	return PyNumber_Subtract(v.get(), other);
+	return apply(first, second, PyNumber_Subtract);
 	JP_PY_CATCH(NULL);  // GCOVR_EXCL_LINE
 }
 
-static  PyObject *PyJPChar_mult(PyJPChar *self, PyObject *other)
+static  PyObject *PyJPChar_mult(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_mult");
 	PyJPModule_getContext();  // Check that JVM is running
-	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
-	if (assertNotNull(javaSlot))
-		return 0;
-
-	// Promote to int as per Java rules
-	JPPyObject v = JPPyObject::call(PyLong_FromLong(fromJPChar(self)));
-	return PyNumber_Multiply(v.get(), other);
+	return apply(first, second, PyNumber_Multiply);
 	JP_PY_CATCH(NULL);  // GCOVR_EXCL_LINE
 }
 
-static  PyObject *PyJPChar_rshift(PyJPChar *self, PyObject *other)
+static  PyObject *PyJPChar_rshift(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_rshift");
 	PyJPModule_getContext();  // Check that JVM is running
-	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
-	if (assertNotNull(javaSlot))
-		return 0;
-
-	// Promote to int as per Java rules
-	JPPyObject v = JPPyObject::call(PyLong_FromLong(fromJPChar(self)));
-	return PyNumber_Rshift(v.get(), other);
+	return apply(first, second, PyNumber_Rshift);
 	JP_PY_CATCH(NULL);  // GCOVR_EXCL_LINE
 }
 
-static  PyObject *PyJPChar_lshift(PyJPChar *self, PyObject *other)
+static  PyObject *PyJPChar_lshift(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_lshift");
 	PyJPModule_getContext();  // Check that JVM is running
-	JPValue *javaSlot = PyJPValue_getJavaSlot((PyObject*) self);
-	if (assertNotNull(javaSlot))
-		return 0;
-
-	// Promote to int as per Java rules
-	JPPyObject v = JPPyObject::call(PyLong_FromLong(fromJPChar(self)));
-	return PyNumber_Lshift(v.get(), other);
+	return apply(first, second, PyNumber_Lshift);
 	JP_PY_CATCH(NULL);  // GCOVR_EXCL_LINE
 }
 
-static  PyObject *PyJPChar_floordiv(PyObject *self, PyObject *other)
+static  PyObject *PyJPChar_floordiv(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_floordiv");
 	PyJPModule_getContext();  // Check that JVM is running
-	JPValue *javaSlot = PyJPValue_getJavaSlot(self);
-	if (javaSlot == NULL)
-	{
-		javaSlot = PyJPValue_getJavaSlot(other);
-		if (assertNotNull(javaSlot))
-			return 0;
-		JPPyObject v = JPPyObject::call(PyLong_FromLong(fromJPChar((PyJPChar*) other)));
-		return PyNumber_FloorDivide(self, v.get());
-	}
-	if (assertNotNull(javaSlot))
-		return 0;
-
-	// Promote to int as per Java rules
-	JPPyObject v = JPPyObject::call(PyLong_FromLong(fromJPChar((PyJPChar*) self)));
-	return PyNumber_FloorDivide(v.get(), other);
+	return apply(first, second, PyNumber_FloorDivide);
 	JP_PY_CATCH(NULL);  // GCOVR_EXCL_LINE
 }
 
-static  PyObject *PyJPChar_divmod(PyObject *self, PyObject *other)
+static  PyObject *PyJPChar_divmod(PyObject *first, PyObject *second)
 {
 	JP_PY_TRY("PyJPChar_divmod");
 	PyJPModule_getContext();  // Check that JVM is running
-	JPValue *javaSlot = PyJPValue_getJavaSlot( self);
-	if (javaSlot == NULL)
-	{
-		javaSlot = PyJPValue_getJavaSlot(other);
-		if (assertNotNull(javaSlot))
-			return 0;
-		JPPyObject v = JPPyObject::call(PyLong_FromLong(fromJPChar((PyJPChar*) other)));
-		return PyNumber_Divmod(self, v.get());
-	}
-	if (assertNotNull(javaSlot))
-		return 0;
-
-	// Promote to int as per Java rules
-	JPPyObject v = JPPyObject::call(PyLong_FromLong(fromJPChar((PyJPChar*) self)));
-	return PyNumber_Divmod(v.get(), other);
+	return apply(first, second, PyNumber_Divmod);
 	JP_PY_CATCH(NULL);  // GCOVR_EXCL_LINE
 }
 
diff --git a/native/python/pyjp_class.cpp b/native/python/pyjp_class.cpp
index 87fd68584..2b9bab9da 100644
--- a/native/python/pyjp_class.cpp
+++ b/native/python/pyjp_class.cpp
@@ -702,14 +702,10 @@ static bool PySlice_CheckFull(PyObject *item)
 	if (!PySlice_Check(item))
 		return false;
 	Py_ssize_t start, stop, step;
-#if PY_VERSION_HEX<0x03060100
-	int rc = PySlice_GetIndices(item, 0x7fffffff, &start, &stop, &step);
-	return (rc == 0)&&(start == 0)&&(step == 1)&&((int) stop == 0x7fffffff);
-#elif defined(ANDROID)
 	int rc = PySlice_Unpack(item, &start, &stop, &step);
+#if defined(ANDROID)
 	return (rc == 0)&&(start == 0)&&(step == 1)&&((int) stop >= 0x7fffffff);
 #else
-	int rc = PySlice_Unpack(item, &start, &stop, &step);
 	return (rc == 0)&&(start == 0)&&(step == 1)&&((int) stop == -1);
 #endif
 }
diff --git a/native/python/pyjp_method.cpp b/native/python/pyjp_method.cpp
index 0d20ee0a8..cfa7640e0 100644
--- a/native/python/pyjp_method.cpp
+++ b/native/python/pyjp_method.cpp
@@ -400,7 +400,8 @@ void PyJPMethod_initType(PyObject* module)
 	unsigned long flags = PyFunction_Type.tp_flags;
 	PyFunction_Type.tp_flags |= Py_TPFLAGS_BASETYPE;
 	PyJPMethod_Type = (PyTypeObject*) PyType_FromSpecWithBases(&methodSpec, tuple.get());
-	PyFunction_Type.tp_flags = flags;	JP_PY_CHECK();
+	PyFunction_Type.tp_flags = flags;
+	JP_PY_CHECK();
 
 	PyModule_AddObject(module, "_JMethod", (PyObject*) PyJPMethod_Type);
 	JP_PY_CHECK();
diff --git a/native/python/pyjp_module.cpp b/native/python/pyjp_module.cpp
index d0684e323..669b3e8fa 100644
--- a/native/python/pyjp_module.cpp
+++ b/native/python/pyjp_module.cpp
@@ -714,20 +714,12 @@ PyMODINIT_FUNC PyInit__jpype()
 	JP_PY_TRY("PyInit__jpype");
 	JPContext_global = new JPContext();
 
-#if PY_VERSION_HEX<0x03070000
-	// This is required for python versions prior to 3.7.
-	// It is called by the python initialization starting from 3.7,
-	// but is safe to call afterwards.  Starting 3.9 this issues a 
-	// deprecation warning.
-	PyEval_InitThreads();
-#endif
-
 	// Initialize the module (depends on python version)
 	PyObject* module = PyModule_Create(&moduledef);
 	// PyJPModule = module;
 	Py_INCREF(module);
 	PyJPModule = module;
-	PyModule_AddStringConstant(module, "__version__", "1.3.0");
+	PyModule_AddStringConstant(module, "__version__", "1.4.1");
 	
 	// Our module will be used for PyFrame object and it is a requirement that
 	// we have a builtins in our dictionary.
diff --git a/native/python/pyjp_value.cpp b/native/python/pyjp_value.cpp
index 6947a7631..93dba9505 100644
--- a/native/python/pyjp_value.cpp
+++ b/native/python/pyjp_value.cpp
@@ -43,8 +43,28 @@ PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems )
 	JP_PY_TRY("PyJPValue_alloc");
 	// Modification from Python to add size elements
 	const size_t size = _PyObject_VAR_SIZE(type, nitems + 1) + sizeof (JPValue);
-	PyObject *obj = (PyType_IS_GC(type)) ? _PyObject_GC_Malloc(size)
-			: (PyObject *) PyObject_MALLOC(size);
+	PyObject *obj = NULL;
+	if (PyType_IS_GC(type))
+	{
+		// Horrible kludge because python lacks an API for allocating a GC type with extra memory
+		// The private method _PyObject_GC_Alloc is no longer visible, so we are forced to allocate
+		// a different type with the extra memory and then hot swap the type to the real one.
+		PyTypeObject type2;
+		type2.tp_basicsize = size;
+		type2.tp_itemsize = 0;
+		type2.tp_name = NULL;
+		type2.tp_flags = type->tp_flags;
+		type2.tp_traverse = type->tp_traverse;
+
+		// Allocate the fake type
+		obj = PyObject_GC_New(PyObject, &type2);
+
+		// Note the object will be inited twice which should not leak. (fingers crossed)
+	}
+	else
+	{
+		obj = (PyObject*) PyObject_MALLOC(size);
+	}
 	if (obj == NULL)
 		return PyErr_NoMemory(); // GCOVR_EXCL_LINE
 	memset(obj, 0, size);
@@ -305,4 +325,4 @@ bool PyJPValue_isSetJavaSlot(PyObject* self)
 		return false;  // GCOVR_EXCL_LINE
 	JPValue* slot = (JPValue*) (((char*) self) + offset);
 	return slot->getClass() != NULL;
-}
\ No newline at end of file
+}
diff --git a/project/debug/unix/README.rst b/project/debug/unix/README.rst
index 0f79ea956..99c7b4d85 100644
--- a/project/debug/unix/README.rst
+++ b/project/debug/unix/README.rst
@@ -121,3 +121,22 @@ If the library does not match it should produce something like::
     ./a.out: error while loading shared libraries: libjvm.so: cannot open shared object file: No such file or directory
 
 
+Two different libstdc++ versions
+--------------------------------
+
+
+If you are compiling the source on a test environment and pushing the compiled Jpype source to a different environment (Unix/Linux) you might see a situation where running::
+
+         ldd libstdc++.so.6 
+
+links to a different version of your production environment. 
+
+You can rememdy this by copying the so file to your production system and creating a soft link to the file with::
+
+         ln -s libstdc++.so.6.0.29 libstdc++.so.6
+
+Then override the system's (in the example below, Oracle/Solaris) 64bit dynamic linker path with::
+
+         export LD_LIBRARY_PATH_64=/path/to/linked/file/above
+
+NOTE: You will want to include the path to any other .so files you will be needing.
diff --git a/setup.py b/setup.py
index a80c5bf91..e7664ef33 100644
--- a/setup.py
+++ b/setup.py
@@ -23,9 +23,6 @@
 from setuptools import Extension
 import glob
 
-if sys.version_info[0] < 3 and sys.version_info[1] < 5:
-    raise RuntimeError("JPype requires Python 3.5 or later")
-
 import setupext
 
 
@@ -40,8 +37,8 @@
     include_dirs=[Path('native', 'common', 'include'),
                   Path('native', 'python', 'include'),
                   Path('native', 'embedded', 'include')],
-    sources=sorted(map(str, list(Path('native', 'common').glob('*.cpp'))+
-             list(Path('native', 'python').glob('*.cpp'))+
+    sources=sorted(map(str, list(Path('native', 'common').glob('*.cpp')) +
+             list(Path('native', 'python').glob('*.cpp')) +
              list(Path('native', 'embedded').glob('*.cpp')))), platform=platform,
 ))
 jpypeJar = Extension(name="org.jpype",
@@ -53,7 +50,7 @@
 
 setup(
     name='JPype1',
-    version='1.3.0',
+    version='1.4.1',
     description='A Python to Java bridge.',
     long_description=open('README.rst').read(),
     license='License :: OSI Approved :: Apache Software License',
@@ -61,6 +58,7 @@
     author_email='devilwolf@users.sourceforge.net',
     maintainer='Luis Nell',
     maintainer_email='cooperate@originell.org',
+    python_requires=">=3.7",
     url='https://github.com/jpype-project/jpype',
     platforms=[
         'Operating System :: Microsoft :: Windows',
@@ -70,17 +68,18 @@
     ],
     classifiers=[
         'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.5',
-        'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3.9',
+        'Programming Language :: Python :: 3.10',
         'Topic :: Software Development',
         'Topic :: Scientific/Engineering',
     ],
     packages=['jpype', 'jpype._pyinstaller'],
     package_dir={'jpype': 'jpype', },
     package_data={'jpype': ['*.pyi']},
-    install_requires=['typing_extensions ; python_version< "3.8"'],
+    install_requires=['typing_extensions ; python_version< "3.8"',
+        'packaging'],
     tests_require=['pytest'],
     extras_require={
         'tests': [
diff --git a/setupext/platform.py b/setupext/platform.py
index fb0260b72..37f27e668 100644
--- a/setupext/platform.py
+++ b/setupext/platform.py
@@ -50,9 +50,9 @@ def Platform(include_dirs=None, sources=None, platform=sys.platform):
                 found_jni = True
                 break
 
-            if not found_jni:
-                distutils.log.warn('Falling back to provided JNI headers, since your provided'
-                                   ' JAVA_HOME "%s" does not provide jni.h', java_home)
+        if not found_jni:
+            distutils.log.warn('Falling back to provided JNI headers, since your provided'
+                               ' JAVA_HOME "%s" does not provide jni.h', java_home)
 
     if not found_jni:
         platform_specific['include_dirs'] += [fallback_jni]
@@ -95,6 +95,10 @@ def Platform(include_dirs=None, sources=None, platform=sys.platform):
     elif platform.startswith('freebsd'):
         distutils.log.info("Add freebsd settings")
         jni_md_platform = 'freebsd'
+     
+    elif platform.startswith('openbsd'):
+        distutils.log.info("Add openbsd settings")
+        jni_md_platform = 'openbsd'
 
     elif platform.startswith('android'):
         distutils.log.info("Add android settings")
@@ -137,6 +141,7 @@ def Platform(include_dirs=None, sources=None, platform=sys.platform):
 ${JAVA_INCLUDE_PATH}/win32
 ${JAVA_INCLUDE_PATH}/linux
 ${JAVA_INCLUDE_PATH}/freebsd
+${JAVA_INCLUDE_PATH}/openbsd
 ${JAVA_INCLUDE_PATH}/solaris
 ${JAVA_INCLUDE_PATH}/hp-ux
 ${JAVA_INCLUDE_PATH}/alpha
diff --git a/setupext/test_java.py b/setupext/test_java.py
index c08780bec..5e8383a36 100644
--- a/setupext/test_java.py
+++ b/setupext/test_java.py
@@ -53,7 +53,7 @@ def compileJava():
     srcs = glob.glob('test/harness/jpype/**/*.java', recursive=True)
     srcs.extend(glob.glob('test/harness/org/**/*.java', recursive=True))
     exports = ""
-    if version > 7:
+    if version == 8:
         srcs.extend(glob.glob('test/harness/java8/**/*.java', recursive=True))
     if version > 8:
         srcs.extend(glob.glob('test/harness/java9/**/*.java', recursive=True))
@@ -87,4 +87,4 @@ def run(self):
         cmdStr = compileJava()
         self.announce("  %s" % " ".join(cmdStr), level=distutils.log.INFO)
         subprocess.check_call(cmdStr)
-        subprocess.check_call(shlex.split("javadoc test/harness/jpype/doc/Test.java -d test/classes/"))
+        subprocess.check_call(shlex.split("javadoc -Xdoclint:none test/harness/jpype/doc/Test.java -d test/classes/"))
diff --git a/test-requirements.txt b/test-requirements.txt
index 5d8de99c2..0fa49257f 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,3 +1,3 @@
-pytest==4.6.9
-pyinstaller==4.0
-jedi==0.17.0
+pytest
+pyinstaller
+jedi==0.18.0
diff --git a/test/harness/jpype/doc/Test.java b/test/harness/jpype/doc/Test.java
index 5108b3c02..a2de9ec68 100644
--- a/test/harness/jpype/doc/Test.java
+++ b/test/harness/jpype/doc/Test.java
@@ -32,7 +32,7 @@ public class Test
    *    hey.method(1, SecretMap());
    * }</pre></blockquote>
    * <p>
-   * <table style="width:100%" summary="Test table">
+   * <table style="width:100%" summary="foo">
    * <tr><th>North</th><th>East</th><th>Steps</th></tr>
    * <tr><td>Y</td><td>N</td><td>10</td></tr>
    * <tr><td>N</td><td>Y</td><td>20</td></tr>
@@ -54,7 +54,7 @@ public class Test
    *
    * @throws java.lang.NoSuchMethodException if something bad happens.
    * @param i an argument <code>a</code>. {@value #QQ}
-   * @param j
+   * @param j another argument.
    * @return a secret code.
    * @since 1.5
    *
diff --git a/test/harness/jpype/overloads/Test2.java b/test/harness/jpype/overloads/Test2.java
new file mode 100644
index 000000000..304e34418
--- /dev/null
+++ b/test/harness/jpype/overloads/Test2.java
@@ -0,0 +1,48 @@
+/* ****************************************************************************
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+  See NOTICE file for details.
+**************************************************************************** */
+package jpype.overloads;
+
+import java.util.List;
+
+public class Test2
+{
+    @FunctionalInterface
+    public interface NoArg {
+        public String apply();
+    }
+
+    @FunctionalInterface
+    public interface SingleArg {
+        public String apply(int a);
+    }
+
+    @FunctionalInterface
+    public interface TwoArg {
+        public String apply(int a, int b);
+    }
+
+    public String testFunctionalInterfaces(NoArg noArg) {
+        return "NoArgs";
+    }
+
+    public String testFunctionalInterfaces(SingleArg singleArg) {
+        return "SingleArg";
+    }
+
+    public String testFunctionalInterfaces(TwoArg twoArg) {
+        return "TwoArg";
+    }
+}
\ No newline at end of file
diff --git a/test/harness/jpype/str/Test.java b/test/harness/jpype/str/Test.java
index 3552501d1..5803b15f7 100644
--- a/test/harness/jpype/str/Test.java
+++ b/test/harness/jpype/str/Test.java
@@ -31,6 +31,14 @@ public String memberCall()
     return "memberCall";
   }
 
+  public static String callWithNullBytes() {
+    return "call\0With\0Null\0Bytes";
+  }
+
+  public static String returnArgument(String argument) {
+    return argument;
+  }
+
   public static final String array[] =
   {
     "apples", "banana", "cherries", "dates", "elderberry"
diff --git a/test/jpypetest/test_array.py b/test/jpypetest/test_array.py
index 1f35461d0..278cc1b10 100644
--- a/test/jpypetest/test_array.py
+++ b/test/jpypetest/test_array.py
@@ -387,7 +387,7 @@ def checkArrayOf(self, jtype, dtype, mn=None, mx=None):
         self.assertTrue(np.all(a[:, :, 4:-2] == JArray.of(a[:, :, 4:-2])))
 
     def checkArrayOfCast(self, jtype, dtype):
-        a = np.random.randint(0, 1, size=100, dtype=np.bool)
+        a = np.random.randint(0, 1, size=100, dtype=np.bool_)
         ja = JArray.of(a, dtype=jtype)
         self.assertIsInstance(ja, JArray(jtype))
         self.assertTrue(np.all(a.astype(dtype) == ja))
@@ -434,7 +434,7 @@ def checkArrayOfCast(self, jtype, dtype):
 
     @common.requireNumpy
     def testArrayOfBoolean(self):
-        self.checkArrayOf(JBoolean, np.bool, 0, 1)
+        self.checkArrayOf(JBoolean, np.bool_, 0, 1)
 
     @common.requireNumpy
     def testArrayOfByte(self):
@@ -462,7 +462,7 @@ def testArrayOfDouble(self):
 
     @common.requireNumpy
     def testArrayOfBooleanCast(self):
-        self.checkArrayOfCast(JBoolean, np.bool)
+        self.checkArrayOfCast(JBoolean, np.bool_)
 
     @common.requireNumpy
     def testArrayOfByteCast(self):
diff --git a/test/jpypetest/test_buffer.py b/test/jpypetest/test_buffer.py
index 0ce730fb6..0da865232 100644
--- a/test/jpypetest/test_buffer.py
+++ b/test/jpypetest/test_buffer.py
@@ -71,10 +71,24 @@ def testLongToBytes(self):
         ja = JArray(JLong)(data)
         self.assertEqual(len(bytes(ja)), 6 * 8)
 
-    def testMemoryViewWrite(self):
+    def testMemoryViewWriteShort(self):
+        data = [1, 2, 3, 4, 5]
+        ja = JArray(JShort)(data)
+        b = memoryview(ja).cast("B")  # Create a view
+        with self.assertRaises(TypeError):
+            b[0] = 123  # Alter the memory using the view
+
+    def testMemoryViewWriteInt(self):
         data = [1, 2, 3, 4, 5]
         ja = JArray(JInt)(data)
-        b = memoryview(ja)  # Create a view
+        b = memoryview(ja).cast("B")  # Create a view
+        with self.assertRaises(TypeError):
+            b[0] = 123  # Alter the memory using the view
+
+    def testMemoryViewWriteLong(self):
+        data = [1, 2, 3, 4, 5]
+        ja = JArray(JLong)(data)
+        b = memoryview(ja).cast("B")  # Create a view
         with self.assertRaises(TypeError):
             b[0] = 123  # Alter the memory using the view
 
@@ -97,7 +111,7 @@ def testSlice(self):
 
     def executeConvert(self, jtype, dtype):
         n = 100
-        na = np.random.randint(0, 1, size=n).astype(np.bool)
+        na = np.random.randint(0, 1, size=n).astype(np.bool_)
         self.assertTrue(np.all(np.array(jtype(na), dtype=dtype)
                                == np.array(na, dtype=dtype)))
         na = np.random.randint(-2**7, 2**7 - 1, size=n, dtype=np.int8)
@@ -122,8 +136,8 @@ def executeConvert(self, jtype, dtype):
         self.assertTrue(np.all(np.array(jtype(na), dtype=dtype)
                                == np.array(na, dtype=dtype)))
         na = np.random.randint(0, 2**64 - 1, size=n, dtype=np.uint64)
-        self.assertTrue(np.all(np.array(jtype(na), dtype=dtype)
-                               == np.array(na, dtype=dtype)))
+        self.assertTrue(np.allclose(np.array(jtype(na), dtype=dtype),
+                               np.array(na, dtype=dtype), atol=1e-8))
         na = np.random.random(n).astype(np.float32)
         self.assertTrue(np.all(np.array(jtype(na), dtype=dtype)
                                == np.array(na, dtype=dtype)))
@@ -133,7 +147,7 @@ def executeConvert(self, jtype, dtype):
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testBoolConvert(self):
-        self.executeConvert(JArray(JBoolean), np.bool)
+        self.executeConvert(JArray(JBoolean), np.bool_)
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testByteConvert(self):
@@ -163,11 +177,12 @@ def executeIntTest(self, jtype, limits, size, dtype, code):
         data = np.random.randint(limits[0], limits[1], size=size, dtype=dtype)
         a = JArray(jtype, data.ndim)(data.tolist())
         u = np.array(a)
-        self.assertTrue(np.all(data == u))
         mv = memoryview(a)
-        self.assertEqual(mv.format, code)
+        self.assertEqual(mv.format, code, "Type issue %s" % type(a))
         self.assertEqual(mv.shape, data.shape)
         self.assertTrue(mv.readonly)
+        self.assertEqual(u.dtype.type, dtype, "Problem with %s %s" % (jtype, dtype))
+        self.assertTrue(np.all(data == u))
 
     def executeFloatTest(self, jtype, size, dtype, code):
         data = np.random.rand(*size).astype(dtype)
@@ -181,7 +196,7 @@ def executeFloatTest(self, jtype, size, dtype, code):
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testBooleanToNP1D(self):
-        self.executeIntTest(JBoolean, [0, 1], (100,), np.bool, "?")
+        self.executeIntTest(JBoolean, [0, 1], (100,), np.bool_, "?")
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testCharToNP1D(self):
@@ -197,11 +212,11 @@ def testShortToNP1D(self):
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testIntToNP1D(self):
-        self.executeIntTest(JInt, [-2**31, 2**31 - 1], (100,), np.int32, "i")
+        self.executeIntTest(JInt, [-2**31, 2**31 - 1], (100,), np.int32, "=i")
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testLongToNP1D(self):
-        self.executeIntTest(JLong, [-2**63, 2**63 - 1], (100,), np.int64, "q")
+        self.executeIntTest(JLong, [-2**63, 2**63 - 1], (100,), np.int64, "=q")
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testFloatToNP1D(self):
@@ -213,7 +228,7 @@ def testDoubleToNP1D(self):
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testBooleanToNP2D(self):
-        self.executeIntTest(JBoolean, [0, 1], (11, 10), np.bool, "?")
+        self.executeIntTest(JBoolean, [0, 1], (11, 10), np.bool_, "?")
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testCharToNP2D(self):
@@ -229,11 +244,11 @@ def testShortToNP2D(self):
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testIntToNP2D(self):
-        self.executeIntTest(JInt, [-2**31, 2**31 - 1], (11, 10), np.int32, "i")
+        self.executeIntTest(JInt, [-2**31, 2**31 - 1], (11, 10), np.int32, "=i")
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testLongToNP2D(self):
-        self.executeIntTest(JLong, [-2**63, 2**63 - 1], (11, 10), np.int64, "q")
+        self.executeIntTest(JLong, [-2**63, 2**63 - 1], (11, 10), np.int64, "=q")
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testFloatToNP2D(self):
@@ -245,7 +260,7 @@ def testDoubleToNP2D(self):
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testBooleanToNP3D(self):
-        self.executeIntTest(JBoolean, [0, 1], (11, 10, 9), np.bool, "?")
+        self.executeIntTest(JBoolean, [0, 1], (11, 10, 9), np.bool_, "?")
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testCharToNP3D(self):
@@ -263,12 +278,12 @@ def testShortToNP3D(self):
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testIntToNP3D(self):
         self.executeIntTest(JInt, [-2**31, 2**31 - 1],
-                            (11, 10, 9), np.int32, "i")
+                            (11, 10, 9), np.int32, "=i")
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testLongToNP3D(self):
         self.executeIntTest(JLong, [-2**63, 2**63 - 1],
-                            (11, 10, 9), np.int64, "q")
+                            (11, 10, 9), np.int64, "=q")
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testFloatToNP3D(self):
@@ -277,3 +292,125 @@ def testFloatToNP3D(self):
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testDoubleToNP3D(self):
         self.executeFloatTest(JDouble, (11, 10, 9), np.float64, "d")
+
+    def executeOrder(self, jtype, dtype):
+        a = np.array([1, 2, 3])
+        ja2 = jtype[:](a)
+        for order in ("=", "<", ">"):
+            dt = np.dtype(dtype).newbyteorder(order)
+            a = np.array([1, 2, 3], dtype=dt)
+            ja = jtype[:](a)
+            self.assertTrue(jpype.java.util.Arrays.equals(ja, ja2), "Order issue with %s %s" % (jtype, dtype))
+
+    @common.requireNumpy
+    def testOrder(self):
+        for i in (jpype.JBoolean, jpype.JByte, jpype.JShort, jpype.JInt, jpype.JLong, jpype.JFloat, jpype.JDouble):
+            for j in (np.int8, np.int16, np.int32, np.int64, np.uint8, np.uint16, np.uint32, np.uint64, np.float32, np.float64):
+                self.executeOrder(i, j)
+        for j in (np.int8, np.int16, np.int32, np.int64, np.uint8, np.uint16, np.uint32, np.uint64):
+            self.executeOrder(jpype.JChar, j)
+
+    def testMemoryByte(self):
+        mv = memoryview(bytes(256))
+        jtype = jpype.JByte[:]
+
+        # Simple checks
+        for dtype in ("c", "?", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "f", "d", "n", "N"):
+            jtype(mv.cast(dtype))
+            jtype(mv.cast("@" + dtype))
+
+        for dtype in ("s", "p", "P", "e"):
+            with self.assertRaises(Exception):
+                jtype(mv.cast(dtype))
+
+    def testMemoryInt(self):
+        mv = memoryview(bytes(256))
+        jtype = jpype.JInt[:]
+
+        # Simple checks
+        for dtype in ("c", "?", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "f", "d", "n", "N"):
+            jtype(mv.cast(dtype))
+            jtype(mv.cast("@" + dtype))
+
+        for dtype in ("s", "p", "P", "e"):
+            with self.assertRaises(Exception):
+                jtype(mv.cast(dtype))
+
+    def testMemoryShort(self):
+        mv = memoryview(bytes(256))
+        jtype = jpype.JShort[:]
+
+        # Simple checks
+        for dtype in ("c", "?", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "f", "d", "n", "N"):
+            jtype(mv.cast(dtype))
+            jtype(mv.cast("@" + dtype))
+
+        for dtype in ("s", "p", "P", "e"):
+            with self.assertRaises(Exception):
+                jtype(mv.cast(dtype))
+
+    def testMemoryLong(self):
+        mv = memoryview(bytes(256))
+        jtype = jpype.JLong[:]
+
+        # Simple checks
+        for dtype in ("c", "?", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "f", "d", "n", "N"):
+            jtype(mv.cast(dtype))
+            jtype(mv.cast("@" + dtype))
+
+        for dtype in ("s", "p", "P", "e"):
+            with self.assertRaises(Exception):
+                jtype(mv.cast(dtype))
+
+    def testMemoryFloat(self):
+        mv = memoryview(bytes(256))
+        jtype = jpype.JFloat[:]
+
+        # Simple checks
+        for dtype in ("c", "?", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "f", "d", "n", "N"):
+            jtype(mv.cast(dtype))
+            jtype(mv.cast("@" + dtype))
+
+        for dtype in ("s", "p", "P", "e"):
+            with self.assertRaises(Exception):
+                jtype(mv.cast(dtype))
+
+    def testMemoryDouble(self):
+        mv = memoryview(bytes(256))
+        jtype = jpype.JDouble[:]
+
+        # Simple checks
+        for dtype in ("c", "?", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "f", "d", "n", "N"):
+            jtype(mv.cast(dtype))
+            jtype(mv.cast("@" + dtype))
+
+        for dtype in ("s", "p", "P", "e"):
+            with self.assertRaises(Exception):
+                jtype(mv.cast(dtype))
+
+    def testMemoryBoolean(self):
+        mv = memoryview(bytes(256))
+        jtype = jpype.JBoolean[:]
+
+        # Simple checks
+        for dtype in ("c", "?", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "f", "d", "n", "N"):
+            jtype(mv.cast(dtype))
+            jtype(mv.cast("@" + dtype))
+
+        for dtype in ("s", "p", "P", "e"):
+            with self.assertRaises(Exception):
+                jtype(mv.cast(dtype))
+
+    def testMemoryChar(self):
+        mv = memoryview(bytes(256))
+        jtype = jpype.JChar[:]
+
+        # Simple checks
+        for dtype in ("c", "?", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "n", "N"):
+            jtype(mv.cast(dtype))
+            jtype(mv.cast("@" + dtype))
+
+        jtype(mv.cast("P"))
+        for dtype in ("s", "p", "f", "d", "@f", "@d"):
+            with self.assertRaises(Exception):
+                jtype(mv.cast(dtype))
diff --git a/test/jpypetest/test_classhints.py b/test/jpypetest/test_classhints.py
index 1c3c9b72b..bcf895622 100644
--- a/test/jpypetest/test_classhints.py
+++ b/test/jpypetest/test_classhints.py
@@ -24,6 +24,21 @@ def blah(self):
         pass
 
 
+class ClassProxy:
+    def __init__(self, proxy):
+        self.proxy = proxy
+
+
+class ArrayProxy:
+    def __init__(self, proxy):
+        self.proxy = proxy
+
+
+class StringProxy:
+    def __init__(self, proxy):
+        self.proxy = proxy
+
+
 class ClassHintsTestCase(common.JPypeTestCase):
 
     def setUp(self):
@@ -85,3 +100,30 @@ def StrToCustom(jcls, args):
         cht.call(MyImpl())
         self.assertIsInstance(cht.input, self.MyCustom)
         self.assertIsInstance(cht.input.arg, MyImpl)
+
+    def testClassCustomizer(self):
+
+        @jpype.JConversion("java.lang.Class", instanceof=ClassProxy)
+        def ClassCustomizer(jcls, obj):
+            return obj.proxy
+
+        hints = jpype.JClass('java.lang.Class')._hints
+        self.assertTrue(ClassProxy in hints.implicit)
+
+    def testArrayCustomizer(self):
+
+        @jpype.JConversion(jpype.JInt[:], instanceof=ArrayProxy)
+        def ArrayCustomizer(jcls, obj):
+            return obj.proxy
+
+        hints = jpype.JClass(jpype.JInt[:])._hints
+        self.assertTrue(ArrayProxy in hints.implicit)
+
+    def testStringCustomizer(self):
+
+        @jpype.JConversion("java.lang.String", instanceof=StringProxy)
+        def STringCustomizer(jcls, obj):
+            return obj.proxy
+
+        hints = jpype.JClass("java.lang.String")._hints
+        self.assertTrue(StringProxy in hints.implicit)
diff --git a/test/jpypetest/test_closeable.py b/test/jpypetest/test_closeable.py
index 74a893591..d8f58a874 100644
--- a/test/jpypetest/test_closeable.py
+++ b/test/jpypetest/test_closeable.py
@@ -17,11 +17,6 @@
 # *****************************************************************************
 import jpype
 import common
-import sys
-
-
-def pythonNewerThan(major, minor):
-    return sys.version_info[0] > major or (sys.version_info[0] == major and sys.version_info[1] > minor)
 
 
 class CloseableTestCase(common.JPypeTestCase):
@@ -38,7 +33,6 @@ def testCloseable(self):
         self.assertEqual(CloseableTest.printed, "hello 1")
         self.assertTrue(CloseableTest.closed)
 
-    @common.unittest.skipUnless(pythonNewerThan(3, 0), "requires python 3")
     def testCloseableFail(self):
         CloseableTest = jpype.JClass("jpype.closeable.CloseableTest")
         CloseableTest.reset()
@@ -72,7 +66,6 @@ def testCloseablePyExcept(self):
         self.assertEqual(CloseableTest.printed, "hello 2")
         self.assertTrue(CloseableTest.closed)
 
-    @common.unittest.skipUnless(pythonNewerThan(2, 6), "Earlier python does not support stacked exceptions.")
     def testCloseablePyExceptFail(self):
         CloseableTest = jpype.JClass("jpype.closeable.CloseableTest")
         CloseableTest.reset()
@@ -105,7 +98,6 @@ def testCloseableJExcept(self):
         self.assertEqual(CloseableTest.printed, "hello 4")
         self.assertTrue(CloseableTest.closed)
 
-    @common.unittest.skipUnless(pythonNewerThan(2, 6), "Earlier python does not support stacked exceptions.")
     def testCloseableJExceptFail(self):
         CloseableTest = jpype.JClass("jpype.closeable.CloseableTest")
         CloseableTest.reset()
diff --git a/test/jpypetest/test_conversionInt.py b/test/jpypetest/test_conversionInt.py
index 94651ba25..bb94ca025 100644
--- a/test/jpypetest/test_conversionInt.py
+++ b/test/jpypetest/test_conversionInt.py
@@ -42,7 +42,7 @@ def testIntFromInt(self):
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testIntFromNPInt(self):
         import numpy as np
-        self.assertEqual(self.Test.callInt(np.int(123)), 123)
+        self.assertEqual(self.Test.callInt(np.int_(123)), 123)
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testIntFromNPInt8(self):
@@ -76,7 +76,7 @@ def testIntFromFloat(self):
     def testIntFromNPFloat(self):
         import numpy as np
         with self.assertRaises(TypeError):
-            self.Test.callInt(np.float(2))
+            self.Test.callInt(np.float_(2))
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testIntFromNPFloat32(self):
diff --git a/test/jpypetest/test_conversionLong.py b/test/jpypetest/test_conversionLong.py
index 2ae7aeae1..ae3f31313 100644
--- a/test/jpypetest/test_conversionLong.py
+++ b/test/jpypetest/test_conversionLong.py
@@ -42,7 +42,7 @@ def testLongFromInt(self):
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testLongFromNPInt(self):
         import numpy as np
-        self.assertEqual(self.Test.callLong(np.int(123)), 123)
+        self.assertEqual(self.Test.callLong(np.int_(123)), 123)
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testLongFromNPInt8(self):
@@ -76,7 +76,7 @@ def testLongFromFloat(self):
     def testLongFromNPFloat(self):
         import numpy as np
         with self.assertRaises(TypeError):
-            self.Test.callLong(np.float(2))
+            self.Test.callLong(np.float_(2))
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testLongFromNPFloat32(self):
diff --git a/test/jpypetest/test_conversionShort.py b/test/jpypetest/test_conversionShort.py
index 43427332c..d3fd81ffa 100644
--- a/test/jpypetest/test_conversionShort.py
+++ b/test/jpypetest/test_conversionShort.py
@@ -42,7 +42,7 @@ def testShortFromInt(self):
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testShortFromNPInt(self):
         import numpy as np
-        self.assertEqual(self.Test.callShort(np.int(123)), 123)
+        self.assertEqual(self.Test.callShort(np.int_(123)), 123)
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testShortFromNPInt8(self):
@@ -76,7 +76,7 @@ def testShortFromFloat(self):
     def testShortFromNPFloat(self):
         import numpy as np
         with self.assertRaises(TypeError):
-            self.Test.callShort(np.float(2))
+            self.Test.callShort(np.float_(2))
 
     @common.unittest.skipUnless(haveNumpy(), "numpy not available")
     def testShortFromNPFloat32(self):
diff --git a/test/jpypetest/test_core.py b/test/jpypetest/test_core.py
index 9bdfa74de..2cec5fbfc 100644
--- a/test/jpypetest/test_core.py
+++ b/test/jpypetest/test_core.py
@@ -25,14 +25,6 @@ class JCharTestCase(common.JPypeTestCase):
     def setUp(self):
         common.JPypeTestCase.setUp(self)
 
-    @mock.patch('jpype._core.sys')
-    def testVersion(self, mock_sys):
-        mock_sys.version_info = (2, 7)
-        with self.assertRaises(ImportError):
-            jpype._core.versionTest()
-        mock_sys.version_info = (3, 8)
-        jpype._core.versionTest()
-
     def testShutdownHook(self):
         Thread = JClass("java.lang.Thread")
         Runnable = JClass("java.lang.Runnable")
diff --git a/test/jpypetest/test_jboolean.py b/test/jpypetest/test_jboolean.py
index c5e391f20..ed0f6bd99 100644
--- a/test/jpypetest/test_jboolean.py
+++ b/test/jpypetest/test_jboolean.py
@@ -69,7 +69,7 @@ def testBooleanFromInt(self):
     @common.requireNumpy
     def testBooleanFromNPInt(self):
         import numpy as np
-        self.assertEqual(self.Test.callBoolean(np.int(123)), True)
+        self.assertEqual(self.Test.callBoolean(np.int_(123)), True)
 
     @common.requireNumpy
     def testBooleanFromNPInt8(self):
@@ -103,7 +103,7 @@ def testBooleanFromFloat(self):
     def testBooleanFromNPFloat(self):
         import numpy as np
         with self.assertRaises(TypeError):
-            self.Test.callBoolean(np.float(2))
+            self.Test.callBoolean(np.float_(2))
 
     @common.requireNumpy
     def testBooleanFromNPFloat32(self):
@@ -130,7 +130,7 @@ def testJArrayConversionBool(self):
     def testSetFromNPBoolArray(self):
         import numpy as np
         n = 100
-        a = np.random.randint(0, 1, size=n, dtype=np.bool)
+        a = np.random.randint(0, 1, size=n, dtype=np.bool_)
         jarr = jpype.JArray(jpype.JBoolean)(n)
         jarr[:] = a
         self.assertCountEqual(a, jarr)
diff --git a/test/jpypetest/test_jbyte.py b/test/jpypetest/test_jbyte.py
index 0cda7c43c..55bfd4ec8 100644
--- a/test/jpypetest/test_jbyte.py
+++ b/test/jpypetest/test_jbyte.py
@@ -77,7 +77,7 @@ def testByteFromInt(self):
     @common.requireNumpy
     def testByteFromNPInt(self):
         import numpy as np
-        self.assertEqual(self.fixture.callByte(np.int(123)), 123)
+        self.assertEqual(self.fixture.callByte(np.int_(123)), 123)
 
     @common.requireNumpy
     def testByteFromNPInt8(self):
@@ -111,7 +111,7 @@ def testByteFromFloat(self):
     def testByteFromNPFloat(self):
         import numpy as np
         with self.assertRaises(TypeError):
-            self.fixture.callByte(np.float(2))
+            self.fixture.callByte(np.float_(2))
 
     @common.requireNumpy
     def testByteFromNPFloat32(self):
diff --git a/test/jpypetest/test_jchar.py b/test/jpypetest/test_jchar.py
index cda4817da..410dbc7a5 100644
--- a/test/jpypetest/test_jchar.py
+++ b/test/jpypetest/test_jchar.py
@@ -472,3 +472,22 @@ def testEq(self):
     def testPass(self):
         fixture = jpype.JClass('jpype.common.Fixture')()
         self.assertEqual(fixture.callObject(self.nc), None)
+
+
+class JCharTestCase(common.JPypeTestCase):
+
+    def testOps(self):
+        self.assertEqual(JChar("x") + "test", "xtest")
+        self.assertEqual("test" + JChar("x"), "testx")
+        self.assertEqual(JChar("x") + 1, 121)
+        self.assertEqual(1 + JChar("x"), 121)
+        self.assertEqual(JChar(1) + JChar("x"), 121)
+        self.assertEqual(JChar("x") - 1, 119)
+        self.assertEqual(1 - JChar("x"), -119)
+        self.assertEqual(JChar("x") - JChar(1), 119)
+        self.assertEqual(JChar("x") * 1, 120)
+        self.assertEqual(1 * JChar("x"), 120)
+        self.assertEqual(JChar("x") & 1, 0)
+        self.assertEqual(1 & JChar("x"), 0)
+        self.assertEqual(JChar("x") | 1, 121)
+        self.assertEqual(1 | JChar("x"), 121)
diff --git a/test/jpypetest/test_jdouble.py b/test/jpypetest/test_jdouble.py
index 34542c327..09c03351b 100644
--- a/test/jpypetest/test_jdouble.py
+++ b/test/jpypetest/test_jdouble.py
@@ -375,7 +375,7 @@ def testArraySetFromNPDouble(self):
 
     @common.requireNumpy
     def testArrayInitFromNPFloat(self):
-        a = np.random.random(100).astype(np.float)
+        a = np.random.random(100).astype(np.float_)
         jarr = JArray(JDouble)(a)
         self.assertElementsAlmostEqual(a, jarr)
 
diff --git a/test/jpypetest/test_jfloat.py b/test/jpypetest/test_jfloat.py
index 4be1e5765..4fbce3591 100644
--- a/test/jpypetest/test_jfloat.py
+++ b/test/jpypetest/test_jfloat.py
@@ -383,7 +383,7 @@ def testArraySetFromNPDouble(self):
 
     @common.requireNumpy
     def testArrayInitFromNPFloat(self):
-        a = np.random.random(100).astype(np.float)
+        a = np.random.random(100).astype(np.float_)
         jarr = JArray(JFloat)(a)
         self.assertElementsAlmostEqual(a, jarr)
 
diff --git a/test/jpypetest/test_jint.py b/test/jpypetest/test_jint.py
index 3293e6f9b..5f09d208e 100644
--- a/test/jpypetest/test_jint.py
+++ b/test/jpypetest/test_jint.py
@@ -166,6 +166,7 @@ def testUnBox(self):
 
     def testFromFloat(self):
         self.assertEqual(JInt._canConvertToJava(1.2345), "explicit")
+
         @jpype.JImplements("java.util.function.IntSupplier")
         class q(object):
             @jpype.JOverride
@@ -357,7 +358,7 @@ def testArrayConversion(self):
 
     @common.requireNumpy
     def testArrayInitFromNPInt(self):
-        a = np.random.randint(-2**31, 2**31 - 1, size=100, dtype=np.int)
+        a = np.random.randint(-2**31, 2**31 - 1, size=100, dtype=np.int_)
         self.checkArrayType(a, a)
 
     @common.requireNumpy
diff --git a/test/jpypetest/test_jlong.py b/test/jpypetest/test_jlong.py
index bfb66f7e3..cd4483bcc 100644
--- a/test/jpypetest/test_jlong.py
+++ b/test/jpypetest/test_jlong.py
@@ -166,6 +166,7 @@ def testUnBox(self):
 
     def testFromFloat(self):
         self.assertEqual(JLong._canConvertToJava(1.2345), "explicit")
+
         @jpype.JImplements("java.util.function.LongSupplier")
         class q(object):
             @jpype.JOverride
@@ -357,7 +358,7 @@ def testArrayConversion(self):
 
     @common.requireNumpy
     def testArrayInitFromNPInt(self):
-        a = np.random.randint(-2**31, 2**31 - 1, size=100, dtype=np.int)
+        a = np.random.randint(-2**31, 2**31 - 1, size=100, dtype=np.int_)
         self.checkArrayType(a, a)
 
     @common.requireNumpy
diff --git a/test/jpypetest/test_jshort.py b/test/jpypetest/test_jshort.py
index 82ad4384c..cb6b679a5 100644
--- a/test/jpypetest/test_jshort.py
+++ b/test/jpypetest/test_jshort.py
@@ -347,7 +347,7 @@ def testArrayConversion(self):
 
     @common.requireNumpy
     def testArrayInitFromNPInt(self):
-        a = np.random.randint(-2**31, 2**31 - 1, size=100, dtype=np.int)
+        a = np.random.randint(-2**31, 2**31 - 1, size=100, dtype=np.int_)
         self.checkArrayType(a, a.astype(np.int16))
 
     @common.requireNumpy
diff --git a/test/jpypetest/test_keywords.py b/test/jpypetest/test_keywords.py
index b70e0f75f..6c255770d 100644
--- a/test/jpypetest/test_keywords.py
+++ b/test/jpypetest/test_keywords.py
@@ -26,4 +26,9 @@ def setUp(self):
 
     def testKeywords(self):
         for kw in keyword.kwlist:
-            self.assertTrue(jpype._pykeywords.pysafe(kw).endswith("_"))
+            safe = jpype._pykeywords.pysafe(kw)
+            if kw.startswith("_"):
+                continue
+            self.assertEqual(type(safe), str, "Fail on keyword %s" % kw)
+            self.assertTrue(safe.endswith("_"))
+        self.assertEqual(jpype._pykeywords.pysafe("__del__"), None)
diff --git a/test/jpypetest/test_legacy.py b/test/jpypetest/test_legacy.py
index 4698d6e85..e0708d0b3 100644
--- a/test/jpypetest/test_legacy.py
+++ b/test/jpypetest/test_legacy.py
@@ -79,3 +79,18 @@ def testProxy(self):
         r = self._test().callProxy(p, "roundtrip")
         self.assertEqual(r, "roundtrip")
         self.assertIsInstance(r, str)
+
+    def testNullBytes(self):
+        s = self._test.callWithNullBytes()
+        self.assertEqual(s, "call\u0000With\u0000Null\u0000Bytes")
+        self.assertIsInstance(s, str)
+
+        s = self._test.returnArgument("\u0394 16 byte encoded text\u0000with null\u0000 bytes \u1394")
+        self.assertEqual(s, "\u0394 16 byte encoded text\u0000with null\u0000 bytes \u1394")
+        self.assertIsInstance(s, str)
+
+        s = self._test.returnArgument("\U0001F468\u200D\U0001F9B2 32 byte encoded text"
+                                      "\u0000with null\u0000 bytes \U0001F468\u200D\U0001F9B2")
+        self.assertEqual(s, "\U0001F468\u200D\U0001F9B2 32 byte encoded text"
+                            "\u0000with null\u0000 bytes \U0001F468\u200D\U0001F9B2")
+        self.assertIsInstance(s, str)
diff --git a/test/jpypetest/test_overloads.py b/test/jpypetest/test_overloads.py
index d33467493..7604ac7ba 100644
--- a/test/jpypetest/test_overloads.py
+++ b/test/jpypetest/test_overloads.py
@@ -24,6 +24,50 @@
 java = jpype.java
 
 
+class MyClass:
+    def fun1(self):
+        pass
+
+    def fun2(self, *a):
+        pass
+
+    def fun3(self, a=1):
+        pass
+
+    def fun4(self, a):
+        pass
+
+
+def fun1():
+    pass
+
+
+def fun2(*a):
+    pass
+
+
+def fun3(a=1):
+    pass
+
+
+def fun4(a):
+    pass
+
+
+if __name__ == '__main__':
+    jpype.startJVM()
+    from java.lang import Runnable
+    mc = MyClass()
+    cb = Runnable @ fun1
+    cb = Runnable @ fun2
+    cb = Runnable @ fun3
+    #cb = Runnable @ fun4
+    cb = Runnable @ mc.fun1
+    cb = Runnable @ mc.fun2
+    cb = Runnable @ mc.fun3
+    cb = Runnable @ mc.fun4
+
+
 class OverloadTestCase(common.JPypeTestCase):
     def setUp(self):
         common.JPypeTestCase.setUp(self)
@@ -84,7 +128,7 @@ def testVarArgsCall(self):
 
     def testPrimitive(self):
         test1 = self.__jp.Test1()
-        intexpectation = 'int' if not sys.version_info[0] > 2 and sys.maxint == 2**31 - 1 else 'long'
+        intexpectation = 'long'
         # FIXME it is not possible to determine if this is bool/char/byte currently
         #self.assertEqual(intexpectation, test1.testPrimitive(5))
         #self.assertEqual('long', test1.testPrimitive(2**31))
@@ -190,3 +234,52 @@ def testDefaultMethods(self):
             pass
         else:
             self.assertEqual('B', testdefault.defaultMethod())
+
+    def testFunctionalInterfacesWithDifferentSignatures(self):
+        test2 = self.__jp.Test2()
+        self.assertEqual('NoArgs', test2.testFunctionalInterfaces(lambda: 'NoArgs'))
+        self.assertEqual('SingleArg', test2.testFunctionalInterfaces(lambda a: 'SingleArg'))
+        self.assertEqual('TwoArg', test2.testFunctionalInterfaces(lambda a, b: 'TwoArg'))
+
+    def testFunctionalInterfacesWithDefaults(self):
+        def my_fun(x, y=None):
+            return 'my_fun'
+
+        def my_fun_vargs(x, *vargs):
+            return 'my_fun_vargs'
+
+        def my_fun_kwargs(x, **kwargs):
+            return 'my_fun_kwargs'
+
+        def my_fun_kw(*, keyword_arg=None):
+            return 'my_fun_kw'
+
+        class my_fun_class:
+            def __call__(self):
+                return 'my_fun_class'
+
+        test2 = self.__jp.Test2()
+        self.assertRaisesRegex(
+            TypeError, 'Ambiguous overloads found', test2.testFunctionalInterfaces, my_fun)
+        self.assertRaisesRegex(
+            TypeError, 'Ambiguous overloads found', test2.testFunctionalInterfaces, my_fun_vargs)
+        self.assertRaisesRegex(
+            TypeError, 'Ambiguous overloads found', test2.testFunctionalInterfaces, my_fun_class)
+        self.assertEqual('SingleArg', test2.testFunctionalInterfaces(my_fun_kwargs))
+        self.assertEqual('NoArgs', test2.testFunctionalInterfaces(my_fun_kw))
+
+    def testRunnable(self):
+        Runnable = jpype.JClass("java.lang.Runnable")
+        mc = MyClass()
+        # These should work
+        cb = Runnable @ fun1
+        cb = Runnable @ fun2
+        cb = Runnable @ fun3
+        cb = Runnable @ mc.fun1
+        cb = Runnable @ mc.fun2
+        cb = Runnable @ mc.fun3
+        # These should fail
+        with self.assertRaises(TypeError):
+            cb = Runnable @ fun4
+        with self.assertRaises(TypeError):
+            cb = Runnable @ mc.fun4
diff --git a/test/jpypetest/test_proxy.py b/test/jpypetest/test_proxy.py
index 8cba2006e..0cba57b52 100644
--- a/test/jpypetest/test_proxy.py
+++ b/test/jpypetest/test_proxy.py
@@ -138,6 +138,7 @@ def testMethod1(self):
     def testProxyImplementsForm2(self):
         itf1 = self.package.TestInterface1
         itf2 = self.package.TestInterface2
+
         @JImplements(itf1, itf2)
         class MyImpl(object):
             @JOverride
@@ -392,6 +393,7 @@ def equals(self, _obj):
 
     def testUnwrap(self):
         fixture = JClass("jpype.common.Fixture")()
+
         @JImplements("java.io.Serializable")
         class Q(object):
             pass
@@ -429,6 +431,7 @@ class R(object):
 
     def testMethods(self):
         fixture = JClass("jpype.common.Fixture")()
+
         @JImplements("java.io.Serializable")
         class R(object):
             pass
@@ -472,6 +475,7 @@ def run(self):
 
     def testWeak(self):
         hc = java.lang.System.identityHashCode
+
         @JImplements("java.io.Serializable")
         class MyObj(object):
             pass
@@ -496,6 +500,14 @@ def testFunctionalLambda(self):
         js = JObject(lambda x: 2 * x, "java.util.function.DoubleUnaryOperator")
         self.assertEqual(js.applyAsDouble(1), 2.0)
 
+    def testBadImplements(self):
+        with self.assertRaises(TypeError):
+            @JImplements("java.lang.Runnable")
+            def MyImpl(object):
+                @JOverride
+                def run(self):
+                    pass
+
 
 @subrun.TestCase(individual=True)
 class TestProxyDefinitionWithoutJVM(common.JPypeTestCase):