Skip to content

Java Interoperability

One of Onion's key strengths is seamless interoperability with Java. You can use any Java library directly in your Onion code.

Importing Java Classes

Import Statement

Use the import statement to import Java classes:

import {
  java.util.ArrayList;
  java.util.HashMap;
  java.io.File;
}

val list: ArrayList[String] = new ArrayList[String]()
val map: HashMap[String, String] = new HashMap[String, String]()
val file: File = new File("data.txt")

Multiple Imports

Group related imports together:

import {
  java.io.File;
  java.io.FileReader;
  java.io.BufferedReader;
  java.io.IOException;
}

Fully Qualified Names

You can use fully qualified names without importing:

val list: java.util.ArrayList[String] = new java.util.ArrayList[String]()
val file: java.io.File = new java.io.File("data.txt")

This also works for static members and methods: call them directly on the dotted class name with ::, no import required:

val m: Int = java.lang.Math::max(3, 7)             // 7
val s: String = java.lang.Integer::toString(255)   // "255"
val mx: Int = java.lang.Integer::MAX_VALUE         // 2147483647

A basic-type keyword (Long, Int, Double, and so on) may also be used as a static receiver; it maps to its boxed class:

val s: String = Long::toString(42L)     // "42"  (java.lang.Long)
val n: Int = Int::parseInt("7")         // 7     (java.lang.Integer)
val mx: Long = Long::MAX_VALUE          // 9223372036854775807

Caveat: this works only for the simple keyword form. A fully-qualified path ending in a keyword — java.lang.Long::toString(42L) — does not parse (the Long segment is a keyword, not an identifier). Use the simple Long:: form above, or import the class under an alias:

import { java.lang.Long as JLong; }

val s: String = JLong::toString(42L)  // "42"

Static Imports

Import static methods from Java classes so you can call them without qualification:

import { java.lang.Math::max; }

println(max(10, 20))  // 20

You can also import an entire class's static members:

import { java.lang.Math }

println(max(10, 20))
println(min(10, 20))
println(abs(-7))

Mix type imports and single-method static imports in the same block:

import {
  java.util.ArrayList;
  java.lang.Math::abs;
}

val xs = new ArrayList[Int]()
xs.add(abs(-5))

Creating Java Objects

Constructor Calls

Create Java objects with the new keyword:

import {
  java.util.ArrayList;
  java.util.Date;
  java.lang.StringBuilder;
}

val list: ArrayList[String] = new ArrayList[String]()
val date: Date = new Date
val builder: StringBuilder = new StringBuilder("Hello")

Constructor with Parameters

import {
  java.io.File;
  java.awt.Dimension;
}

val file: File = new File("/path/to/file.txt")
val size: Dimension = new Dimension(800, 600)

Calling Java Methods

Instance Methods

Call methods on Java objects normally:

import { java.util.ArrayList; }

val list: ArrayList[String] = new ArrayList[String]()
list.add("First")
list.add("Second")
list.add("Third")

val size: Int = list.size()  // 3
val item: Object = list.get(0)  // "First"
val exists: Boolean = list.contains("Second")  // true

Static Methods

Use :: to call static methods:

// Math class
val random: Double = Math::random()
val sqrt: Double = Math::sqrt(16.0)
val max: Int = Math::max(10, 20)

// System class
val time: Long = System::currentTimeMillis()
val property: String = System::getProperty("os.name")

// Integer parsing
val number: Int = JInteger::parseInt("42")

Method Chaining

import { java.lang.StringBuilder; }

val result: String = new StringBuilder("Hello")
  .append(" ")
  .append("World")
  .append("!")
  .toString()

println(result)  // "Hello World!"

Working with Java Collections

ArrayList

import { java.util.ArrayList; }

val list: ArrayList[String] = new ArrayList[String]()
list << "Apple"     // << operator for add()
list << "Banana"
list << "Cherry"

foreach fruit: String in list {
  println(fruit)
}

val first: String = list.get(0) as String
val size: Int = list.size()

HashMap

import { java.util.HashMap; }

val map: HashMap[String, Object] = new HashMap[String, Object]()
map.put("name", "Alice")
map.put("age", 30)
map.put("city", "Tokyo")

val name: String = map.get("name") as String
val age: Int = map.get("age") as Int

val hasKey: Boolean = map.containsKey("name")

HashSet

import { java.util.HashSet; }

val set: HashSet[String] = new HashSet[String]()
set.add("red")
set.add("green")
set.add("blue")
set.add("red")  // Duplicate, won't be added

println(set.size())  // 3

Implementing Java Interfaces

ActionListener Example

import {
  java.awt.event.ActionListener;
  java.awt.event.ActionEvent;
  javax.swing.JButton;
}

class MyButtonHandler <: ActionListener {
  public:
    def actionPerformed(event :ActionEvent) {
      println("Button clicked!")
      val source: JButton = event.getSource() as JButton
      println("Button text: " + source.getText())
    }
}

val button: JButton = new JButton("Click me")
val handler: MyButtonHandler = new MyButtonHandler
button.addActionListener(handler)

Runnable Example

import { java.lang.Runnable; }

class MyTask <: Runnable {
  val name: String

  public:
    def this(n: String) {
      this.name = n
    }

    def run {
      println("Task " + this.name + " is running")
    }
}

val task: MyTask = new MyTask("Background Task")
val thread: Thread = new Thread(task)
thread.start()

Comparable Example

import { java.lang.Comparable; }

class Person <: Comparable[Object] {
  val name: String
  val age: Int

  public:
    def this(n: String, a: Int) {
      this.name = n
      this.age = a
    }

    def compareTo(other: Object): Int {
      val otherPerson: Person = (other as Person)
      return this.age - otherPerson.age
    }
}

Extending Java Classes

JFrame Example

import {
  javax.swing.JFrame;
  javax.swing.JButton;
  java.awt.FlowLayout;
}

class MyWindow : JFrame {
  public:
    def this {
      setTitle("My Window")
      setSize(400, 300)
      setLayout(new FlowLayout())

      val button: JButton = new JButton("Click me")
      add(button)

      setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE)
      setVisible(true)
    }
}

val window: MyWindow = new MyWindow

Java I/O

File Reading

import {
  java.io.File;
  java.io.FileReader;
  java.io.BufferedReader;
  java.io.IOException;
}

def readFile(filename :String) {
  try {
    val reader: BufferedReader = new BufferedReader(
      new FileReader(filename)
    )

    var line: String = null
    while (line = reader.readLine()) != null {
      println(line)
    }

    reader.close()
  } catch e :IOException {
    println("Error reading file: " + e.getMessage())
  }
}

readFile("data.txt")

File Writing

import {
  java.io.FileWriter;
  java.io.BufferedWriter;
  java.io.IOException;
}

def writeFile(filename :String, content :String) {
  try {
    val writer: BufferedWriter = new BufferedWriter(
      new FileWriter(filename)
    )

    writer.write(content)
    writer.newLine()
    writer.close()

    println("File written successfully")
  } catch e :IOException {
    println("Error writing file: " + e.getMessage())
  }
}

writeFile("output.txt", "Hello, World!")

Java Swing GUI

Complete GUI Application

import {
  javax.swing.JFrame;
  javax.swing.JButton;
  javax.swing.JTextField;
  javax.swing.JPanel;
  java.awt.BorderLayout;
  java.awt.event.ActionListener;
  java.awt.event.ActionEvent;
}

class CalculatorApp : JFrame <: ActionListener {
  val textField: JTextField
  var currentValue: Double

  public:
    def this {
      setTitle("Simple Calculator")
      setSize(300, 200)
      setLayout(new BorderLayout())

      this.textField = new JTextField()
      add(this.textField, BorderLayout::NORTH)

      val panel: JPanel = new JPanel()
      val button: JButton = new JButton("Calculate")
      button.addActionListener(this)
      panel.add(button)

      add(panel, BorderLayout::CENTER)

      this.currentValue = 0.0
      setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE)
      setVisible(true)
    }

    def actionPerformed(event :ActionEvent) {
      val text: String = this.textField.getText()
      println("Calculating: " + text)
    }
}

val app: CalculatorApp = new CalculatorApp

Type Casting

Casting Objects

Use the as operator to cast Java objects:

val obj: Object = "Hello"
val str: String = (obj as String)

val list: ArrayList[String] = new ArrayList[String]()
list.add("Item")
val item: String = list.get(0) as String

Numeric Casting

val random: Double = Math::random()
val randomInt: Int = (random * 100) as Int

val longValue: Long = 123456789L
val intValue: Int = (longValue as Int)

Common Java Classes

String Operations

val text: String = "Hello, World!"
val upper: String = text.toUpperCase()
val lower: String = text.toLowerCase()
val length: Int = text.length()
val sub: String = text.substring(0, 5)
val contains: Boolean = text.contains("World")

Integer and Long

// Parsing
val i: Int = JInteger::parseInt("42")
val l: Long = JLong::parseLong("1234567890")

// Conversion
val iStr: String = JInteger::toString(42)
val lStr: String = JLong::toString(1234567890L)

// Min/Max
val max: Int = JInteger::MAX_VALUE
val min: Int = JInteger::MIN_VALUE

Math Operations

val abs: Int = Math::abs(-10)        // 10
val ceil: Double = Math::ceil(3.14)   // 4.0
val floor: Double = Math::floor(3.14) // 3.0
val round: Long = Math::round(3.14)   // 3
val pow: Double = Math::pow(2.0, 3.0) // 8.0

Next Steps