This page contains the information about the typer extensions that allows to access Java elements from OCaml sources, as shipped with the distribution since version 2.0-early-access8.

Warning! these extensions, as well as the associated library, are still at the experimental stage and subject to changes.

Warning! in order to enable the extensions, it is necessary to pass the -java-extensions switch to the ocamljava compiler, and to link with the javalib library. Thus resulting in the following command-line for a one-file application:
ocamljava javalib.cmja -java-extensions source.ml

The same command-line switch should be passed to ocamldoc when generating documentation. This command-line switch can be requested in an ocamlbuild _tags file through the java-extensions tag.

In order to be able to manipulate Java elements from OCaml sources, it is necessary to choose a mapping from Java types to OCaml types. The following table shows how Java primitive types are mapped to OCaml predefined types.

Java typeOCaml type
boolean bool
byte int
char int
double float
float float
int int32
long int64
short int

Java reference types are mapped to two OCaml types, namely java_instance and java_extends. Both are abstract data types accepting one type parameter used to denote a class name. The difference between the two types is that java_instance designates exactly an instance of a given class, while java_extends designates an instance of either that class or any of its subclasses.

A special notation is introduced to specify the class name: the type parameter of either java_instance or java_extends can be:

  • a classical type variable, e.g. 'a;
  • a Java class name, e.g. java'lang'String.
When writing a Java class name, single quotes have to be used instead of dots to abide the OCaml syntactic rules. Thus leading to the type expression java'lang'String java_instance to designate in OCaml an instance of the java.lang.String class.

Once the mapping from Java types to OCaml types is defined, we need mechanisms to create new instances, call methods, and access fields. This is done through functions from the Java module (ocamldoc documentation).

A new instance is built by calling Java.make with a first parameter describing the constructor to be used (as a string literal), the other parameters being the parameters actually passed to the constructor. For example, the following code builds a bare object and an Integer:

let obj = Java.make "java.lang.Object()"
let itg = Java.make "java.lang.Integer(int)" 123l

A similar mechanism is used to invoke methods, through the Java.call function. The first parameter to this function is a descriptor to the method to be called. The other parameters are the parameters passed to the function, including the instance on which the method should be invoked (if the method is not static). For example, the following code retrieves the hash code of the previously-created object and then tests whether the two instances are equal:

let obj_hash = Java.call "java.lang.Object.hashCode():int" obj
let eq = Java.call "java.lang.Object.equals(java.lang.Object):boolean" obj itg

It is noteworthy that the subtyping relationship over Java instances is preserved, so that it is possible to define a function retrieving the hash code, and then apply it to Object and Integer instances:

let hash_code x = Java.call "java.lang.Object.hashCode():int" x
let obj_hash = hash_code obj
let itg_hash = hash_code itg

Accessing fields to read (respectively write) their values is done through the Java.get function (respectively Java.set). The first parameter to the function is a descriptor of the Java field to access, and the second parameter the instance to use (or unit if the field is static). For example, we can retrieve the maximum integer value and and increment the width of a dimension by:

let max_int = Java.get "java.lang.Integer.MAX_VALUE:int" ()
let incr_width dim =
  let w = Java.get "java.awt.Dimension.width:int" dim in
  Java.set "java.awt.Dimension.width:int" dim (succ w)

Finally, it is possible to implement a Java interface with OCaml code through the Java.proxy function. The first parameter to the function is a descriptor designating a Java interface, while the second parameter is an OCaml object implementing the methods specified by the Java interface. The Java.proxy function then returns a fully-functional instance of the interface. For example, the following code implements an action listener and registers it with a previously created button:

let button = Java.make "java.awt.Button()"

let listener =
  Java.proxy "java.awt.event.ActionListener" (object
    method actionPerformed _ =
      print_endline "clicked!"
  end)

let () =
  Java.call "java.awt.Button.addActionListener(java.awt.event.ActionListener):void"
    button listener

So far, we have seen how to create and manipulate Java instances from purely OCaml code. However, the resulting code is quite verbose. We thus introduce some syntactic sugar to allow terser programs. Firstly, it is possible to remove the return type of a method or the type of a field as long as there is no ambiguity. Secondly, the type of a method parameter can be replaced by an underscore provided there is no ambiguity. The combination of these rules already allows us to switch from

let eq = Java.call "java.lang.Object.equals(java.lang.Object):boolean" obj itg
to
let eq = Java.call "java.lang.Object.equals(_)" obj itg
It is noteworthy that the types are not affected by these shorthand notations, and that the compiler will issue an error if there is an ambiguity.

Lastly, we introduce a mechanism akin to the Java import pack.* directive through a special form of the OCaml open directive. Importing all the classes of the Java package pack is done by writing open Package'pack (note that, as in Java, the java.lang package is always opened). Thus leading to a revised version of our proxy example:

open Package'java'awt
open Package'java'awt'event

let button = Java.make "Button()"

let listener =
  Java.proxy "ActionListener" (object
    method actionPerformed _ =
      print_endline "clicked!"
  end)

let () = Java.call "Button.addActionListener(_)" button listener
Opened packages also allow to reduce the verbosity of type expressions, allowing to replace the package name by an underscore. The type of the aforementioned hash_code function can then be written:
val hash_code : _'Object java_extends -> int32
rather than:
val hash_code : java'lang'Object java_extends -> int32

The following code builds a simple Celsius-Fahrenheit converter based on a Swing GUI. The code is derived from the Clojure code sample available here. The code uses the JavaString for conversion between Java and OCaml strings.

open Package'java'awt
open Package'java'awt'event
open Package'javax'swing

let () =
   let str = JavaString.of_string in
   let open Java in
   let title = str "Celsius Converter" in
   let frame = make "JFrame(String)" title in
   let temp_text = make "JTextField()" () in
   let celsius_label = make "JLabel(String)" (str "Celsius") in
   let convert_button = make "JButton(String)" (str "Convert") in
   let farenheit_label = make "JLabel(String)" (str "Farenheit") in
   let handler = proxy "ActionListener" (object
     method actionPerformed _ =
       try
         let c = call "JTextField.getText()" temp_text in
         let c = call "Double.parseDouble(_)" c in
         let f = (c *. 1.8) +. 32.0 in
         let f = Printf.sprintf "%f Farenheit" f in
         call "JLabel.setText(_)" farenheit_label (str f)
       with Java_exception je ->
         let je_msg = call "Throwable.getMessage()" je in
         let je_msg = JavaString.to_string je_msg in
         let msg = str (Printf.sprintf "invalid float value (%s)" je_msg) in
         let error = get "JOptionPane.ERROR_MESSAGE" () in
         call "JOptionPane.showMessageDialog(_,_,_,_)"
           frame msg title error
   end) in
   let () = call "JButton.addActionListener(_)" convert_button handler in
   let layout = make "GridLayout(_,_,_,_)" 2l 2l 3l 3l in
   call "JFrame.setLayout(_)" frame layout;
   ignore (call "JFrame.add(Component)" frame temp_text);
   ignore (call "JFrame.add(Component)" frame celsius_label);
   ignore (call "JFrame.add(Component)" frame convert_button);
   ignore (call "JFrame.add(Component)" frame farenheit_label);
   call "JFrame.setSize(_,_)" frame 300l 80l;
   let exit = get "WindowConstants.EXIT_ON_CLOSE" () in
   call "JFrame.setDefaultCloseOperation(int)" frame exit;
   call "JFrame.setVisible(_)" frame true

On the OCaml side, Java exceptions are encapsulated into Java_exception instances whose definition is:

exception Java_exception of java'lang'Throwable java_instance
The Java.instanceof and Java.cast functions can be used to respectively test whether a given object is an instance of a given class, and to cast it to a given class. Both functions accept as their first parameter the name of the class as a string literal. The following code illustrates how OCaml and Java exceptions can be mixed:
open Package'java'io

let f ... =
  try
    ...
  with
  | Not_found ->
    (* predefined OCaml Not_found exception *)
    ...
  | Java_exception t when Java.instanceof "IOException" t ->
    (* Java exception that is a subclass of java.io.IOException *)
    ...
  | Java_exception _ ->
    (* any other Java exception *)
    ...
  | _ ->
    (* any other OCaml exception *)
    ...

Symmetrically, Java exceptions are raised from OCaml code by calling the Java.throw function with a parameter of type java'lang'Throwable java_extends.

For effiency reasons, Java arrays are mapped to specialized implementations, as shown by the following table:

Java typeOCaml type
boolean[] int java_boolean_array = int JavaBooleanArray.t
byte[] int java_byte_array = int JavaByteArray.t
char[] int java_char_array = int JavaCharArray.t
double[] float java_double_array = float JavaDoubleArray.t
float[] float java_float_array = int JavaFloatArray.t
int[] int32 java_int_array = int32 JavaIntArray.t
long[] int64 java_long_array = int64 JavaLongArray.t
short[] int java_short_array = int JavaShortArray.t
reference[] reference java_reference_array = reference JavaReferenceArray.t
note1: each array type has a type parameter representing the OCaml type of array elements.
note2: all primitive array types share a common signature, namely JavaArraySignature.T.

Array instances are created through either the Java.make_array function, or any of the make functions from the various modules. The Java.make_array function accepts as its first parameter an array descriptor, and as additional parameters int32 values for the various dimensions. For example, a 2x3 byte matrix can be created through:

let m = Java.make_array "byte[][]" 2l 3l

It is also possible to use a uniform representation for arrays by wrapping array instances into JavaArray.t values. The JavaArray.t type is a GADT that unifies all specialized arrays types into one common type. This allows to write generic code over arrays, at the price of an indirection. The JavaArray modules provides the functions to wrap the various kinds of arrays into JavaArray.t values.