Use jpy

jpy is a bidirectional Java-Python bridge. The Deephaven query engine is implemented in Java, and jpy is the bridge that lets Deephaven's Python API call it. That makes it relatively easy to use Java from within Python.

This guide shows how to use Java from Python. You'll learn how to:

  • Get Java types.
  • Call their methods.
  • Create Java arrays.
  • Pass Deephaven tables to Java methods and wrap the tables they return.
  • Use Java objects in query strings.

It doesn't cover calling Python from Java, which is much less common.

For more details on jpy, see the jpy GitHub project, or use help("jpy") to see the package documentation.

Caution

jpy is a low-level tool. Use it only when Deephaven has no Python wrapper for the Java objects you need. A Python wrapper is a Python module or class that calls the Java object for you.

For many tasks in this guide, the deephaven.dtypes module is the better choice. It defines Deephaven's Java data types, including the primitives, and creates Java arrays and instances of many Java object types. This guide uses primitive types and simple objects to show how jpy works, not to recommend it for those cases.

Note

To request a Python wrapper for a particular Java type or library, open an issue in the Deephaven Core GitHub repository.

Get Java types

Most tasks on this page start with a Java type, which jpy.get_type retrieves by name.

Use jpy.get_type

To get a Java type from Python, pass the fully qualified class name of the desired type as a string argument to jpy.get_type. Here's a simple example using java.lang.String.

By convention, this guide prefixes the names of Java types with _J (for example, _JString), and the names of Java tables returned by Java methods with _j (for example, _j_merged_t). jpy doesn't require either prefix.

jpy.get_type can get any valid Java type that is present in the Java classpath, including Deephaven-specific types. To make a third-party library available, see Install and use Java packages. Here is an example using Deephaven's DoubleVector.

What does get_type return?

jpy.get_type returns a unique Python class that wraps the underlying Java type. You can inspect this class with familiar Python functions.

jpy gives this class methods that mirror those of the underlying Java type. To print the full class, including every Java method, call Python's help function: help(_JURL). Its output is long, so the following example prints only the names of the Java methods:

Check the Javadoc for java.net.URL to confirm that the jpy class includes all of its public methods. jpy doesn't expose protected or private members.

Access nested (inner) classes

Java code refers to an inner class with dot notation: OuterClass.InnerClass. However, jpy.get_type expects the class's binary name, which separates the outer and inner class names with $. This is the same form that Class.forName takes. Dot notation fails.

Use $ instead:

The pattern is jpy.get_type("fully.qualified.OuterClass$InnerClass"). Each additional level of nesting adds another $. For example, a class nested two levels deep is OuterClass$InnerClass$DeepInner.

Here are examples using Deephaven types:

Note

To find the outer and inner class names, look in the Java source or Javadoc for the nested class or interface declared inside the outer class definition, such as interface Entry inside Map.

Call methods on Java types

Python code can use Java methods directly.

Call static methods

Static methods are methods that belong to the type rather than to an instance of the type. You can call a static method directly on the type that jpy.get_type returns. This example uses the toString static method from the java.lang.Integer type.

Create objects and call instance methods

To create an instance of a Java type, call the type that jpy.get_type returns like a function. This invokes one of the type's constructors. The code block below creates a java.math.BigDecimal with its String constructor.

Unlike static methods, which belong to the type, instance methods belong to an instance and operate on the data stored in that object.

decimal_instance is a Java object and provides many instance methods for operating on the value 123.456. The Javadoc for java.math.BigDecimal details all of the available methods. This example uses the toString, compareTo, and equals instance methods.

Return type conversion

jpy converts some Java return values to Python values. It converts a value when the method declares one of the types below as its return type, or declares java.lang.Object and returns one of them:

  • A primitive or a boxed primitive, such as java.lang.Integer or java.lang.Float, becomes the matching Python bool, int, or float. A char or java.lang.Character becomes an int holding the char's numeric (UTF-16) value, not a str.
  • A java.lang.String becomes a Python str.
  • null becomes None.

For example, _JInteger.valueOf(4) returns a Python int, not a java.lang.Integer that you can call instance methods on. The next example shows the same conversion for a double: Math.random returns a Java double, which comes back as a Python float.

When a Java method returns an object that jpy can't convert to a Python type, jpy wraps the result in a Python object that you can use like any other.

Argument type conversion

When you call a Java method with Python arguments, such as _JInteger.toString(123), jpy auto-converts the Python objects to Java objects of the appropriate types. Here is another example of this conversion.

Here, 4 and 2 are Python objects, so jpy must convert them to Java values before calling methods.

jpy auto-converts these Python arguments:

  • int and bool values passed to any numeric, char, or boolean primitive parameter, or to its boxed type, such as java.lang.Integer.
  • float values passed to float or double parameters, or to java.lang.Float or java.lang.Double parameters. A float is never converted to an integer type such as int or long.
  • str values passed to String parameters.
  • int, float, bool, and str values passed to Object parameters, or to another type that the matching Java type extends or implements, such as Number or CharSequence.
  • None passed to any object parameter, which becomes null.
  • Python sequences passed to Java array parameters.

jpy also passes most other numeric objects, including NumPy scalars such as np.int32 and np.float32, to float and double parameters. It treats them differently from Python numbers:

  • np.float64 is a subclass of Python float, so it converts exactly like one.
  • Other NumPy scalars, including NumPy integers, never convert to integer parameters such as int or long. For float and double parameters they rank below a Python float, so a method that has both overloads fails as ambiguous. See Overload resolution errors.

Convert NumPy scalars with int() or float() before passing them.

jpy doesn't convert other Python objects, such as a dict. The only parameter type that can receive them is org.jpy.PyObject, which accepts any Python object. To pass one of those objects to any other parameter type, first create a Java object of the parameter's type.

Passing such an object to a java.lang.Object parameter raises a ValueError rather than one of the overload resolution errors. For a dict, the message is cannot convert a Python 'dict' to a Java 'java.lang.Object'.

The dayOfWeek method from Deephaven's DateTimeUtils takes a Java LocalDate. jpy can't convert any Python object to a LocalDate, so this example builds one with DateTimeUtils.parseLocalDate.

Note

This example is only illustrative. In practice, create the LocalDate with to_j_local_date from deephaven.time, or call dayOfWeek directly in a query string.

Method overloading

Java allows method overloading, where multiple methods have the same name but different signatures. Python does not. When you call an overloaded Java method, jpy chooses the overload whose parameter types best match your arguments.

The best match is an exact match, where every argument has the Python type that fits its parameter best:

  • A Python int is an exact match for any integer or char parameter, such as int or long.
  • A Python float is an exact match for a double parameter. It also converts to a float parameter, but ranks lower there.
  • A Python bool is an exact match for a boolean parameter.

This example calls Java's max method twice, once with two Python int values and once with two Python float values:

  • For int arguments, both max(int, int) and max(long, long) are exact matches. jpy calls the first one it finds, and the result is a Python int either way.
  • For float arguments, jpy calls max(double, double), because double is the exact match for a Python float. The result is a Python float.

jpy uses the same matching when you pass a mix of Python and Java arguments. Here's an example that calls two overloads of the minus method with different combinations of Python and Java arguments.

Overload resolution errors

If jpy cannot find an overload matching the argument types provided, it raises a "no matching Java method overloads found" error:

This error occurs when you pass the wrong number of arguments, pass an argument of the wrong type, or pass a Python value that jpy can't auto-convert to the parameter type. The following example makes one call of each kind and prints each error.

jpy raises an "ambiguous Java method call" error if more than one overload matches the arguments equally well and none of them is an exact match:

This happens when you pass NumPy scalars, such as np.int32 values, to a method that has both float and double overloads. The following example triggers the error, then fixes it by converting the arguments to Python int values first.

Create Java arrays

jpy.array creates a Java array from either a Python sequence of values or an integer that sets the array length. You don't need it to pass a Python list to a Java array parameter, because jpy converts the list for you. Use jpy.array when you need the Java array as an object of its own:

  • To build nested arrays.
  • To control the element type exactly.
  • To create the array once and reuse it.

For one-dimensional arrays of Deephaven-supported types, deephaven.dtypes.array does the same job through Deephaven's Python API, so use it instead of jpy.array where it fits.

One-dimensional arrays

To create a one-dimensional array with jpy.array, pass in the name of the type and a Python sequence (such as a list, tuple, or NumPy array) containing elements that can be converted to the target type.

You can also pass the number of elements instead of a sequence. This yields an array of default values: zeros for numeric primitives, false for boolean, and null for objects.

If the sequence contains elements that cannot be converted to the requested Java type, the conversion fails.

jpy.array also creates arrays of non-primitive types, such as java.lang.String and java.lang.Float. Specify the fully qualified class name:

Nested arrays

Multi-dimensional arrays, or nested arrays, are common in Java. To create one, nest calls to jpy.array. The outer call can't take an element type name such as "int", which the One-dimensional arrays examples use. The following example tries this with a nested array of integers and fails.

The outer call fails because it names int as its type, but its elements are int arrays. Name each call's type after what it holds:

  • The innermost calls hold plain values, so they take the element type name, such as "int".
  • Every other call holds arrays, so it takes the name of the array type it holds.

Write an array type name by these rules:

  • For primitive types, use the type signature. The type signatures for each primitive type are:

    Primitive TypeType Signature
    booleanZ
    byteB
    charC
    shortS
    intI
    longJ
    floatF
    doubleD
  • For object types, use the fully qualified class name, prepended with L and ending with ;. Write the class name with dots, as in Ljava.lang.String;.

  • For an n-dimensional array, prepend n-1 open square brackets.

To clarify these rules, here are some concrete examples.

Example 1: 2D boolean array

This example creates a 2D array of boolean values. Because the elements are primitives, use the type signature Z for boolean. Because the array is 2D, prepend one open square bracket: [Z.

Example 2: 3D int array

This example creates a 3D array of integer values. Because int is primitive, use the type signature I. Because the array is 3D, prepend two open square brackets: [[I.

Example 3: 2D java.lang.String array

This example creates a 2D array of String values. Because the elements are objects, prepend L to the class name and append ;. Because the array is 2D, prepend one open square bracket.

Example 4: 3D java.lang.String array

Finally, this example creates a 3D array of String values. This needs L and ;, plus two open square brackets.

Use jpy with Deephaven

Because the Deephaven query engine is implemented in Java, Deephaven makes it easy to use Java objects directly. Deephaven's Python objects expose the Java objects they wrap, so you can pass Deephaven tables to Java methods you call through jpy. In the other direction, Java objects you create with jpy can be used directly in query strings.

The j_object property

To access the Java objects underlying Deephaven's Python API, use the j_object property.

Use Java libraries with Deephaven tables

Many use cases for jpy involve operating on Deephaven tables with Java libraries or methods that have not been wrapped in Python. Use jpy.get_type to get the desired class. Then, pass Deephaven tables to Java methods with the j_object property.

Java methods in Deephaven libraries often return new tables as Java objects. To use one with the rest of the Python API, wrap it with the Table constructor from deephaven.table.

Use Java values in query strings

Java objects created with jpy can be used directly in Deephaven's query strings. For more on using Java objects and their methods in query strings, see Java objects and classes in query strings.

You can call a Java object's methods either outside a query string or inside one.

Compare Java and Python performance in query strings

Java values in query strings don't cross the Python-Java boundary when the query runs. This makes them faster than patterns that do cross it, such as calling a Python function from a query string. Here is an example that compares the efficiency of generating random numbers using a Python or a Java approach.

The Java version finishes faster because each row's nextGaussian call stays in Java, while the Python version runs Python code for every row.

Tie it all together

This example uses ColumnsToRowsTransform to turn a table's value columns into labeled rows. It combines these pieces from earlier sections:

  • jpy.get_type to get the Java class.
  • A call to the static columnsToRows method.
  • jpy.array to build the String[] arguments.
  • The j_object property to pass the table to Java.
  • The Table constructor to wrap the Java table that comes back.