8 breaking releases

Uses new Rust 2024

new 0.9.0 May 12, 2025
0.7.0 May 11, 2025

#289 in Encoding

Download history 250/week @ 2025-04-23 202/week @ 2025-04-30 375/week @ 2025-05-07

827 downloads per month

MIT license

60KB
1.5K SLoC

typeshare-java

typeshare-java is a CLI tool for converting Rust types into Java types. It is part of the wider typeshare ecosystem.

Installation

First, install the CLI:

cargo install typeshare-java

Then, install the annotations:

cargo add typeshare

Feature Support

Feature Status Comment
Structs -
Struct Generics -
Unit Enums -
Algebraic Enums Gson only.
Enum Generics -
Type Aliases -
Constants 🚧 Only with namespace class option.

Data Types

Rust Type Java Type Comment
Vec<T> java.util.ArrayList<T> -
[T; N] T[] There is no fixed length array type in Java.
&[T] T[] -
HashMap<K, V> java.util.HashMap<K, V> -
Option<T> T All types in Java are nullable.
Unit Void -
String String -
char Character -
i8 Byte -
i16 Short -
ISize, i32 Integer -
I54, i64 Long -
u8 Short Byte in Java is signed, so we need to use short to represent all possible values.
u16 Integer Short in Java is signed, so we need to use int to represent all possible values.
u32 Long Integer in Java is signed, so we need to use long to represent all possible values.
u64 java.math.BigInteger Long in Java is signed, so we need to use BigInteger to represent all possible values.
bool Boolean -
f32 Float -
f64 Double -

[!NOTE] We prefer to use classes over primitive types (e.g. Integer over int). This is because primitve types can't be used as generics in Java. In future, we may use primitive types outside of generic contexts.

[!NOTE] In general, for a given Rust type (Tr) we choose a Java type (Tj) such that the Rust type is subset of the Java type (Tr⊆Tj). This way, all possible Rust values can be represented in Java. However, in some cases, it is also possible to represent values in Java which would be invalid in Rust. For example, in Java there is no unsigned 8-bit integer type. Therefore, we use the Short Java type to represent u8 in Rust. All possible u8 values can be represented in a Short, but Short additionally allows negative values. It is up to the developer to ensure that the value used in Java is valid when deserialized in Rust.

Usage

CLI

To get started, run:

cd <your-rust-project>
typeshare-java --output-file output.java ./

Annotations

Getting Started

For typeshare-java to generate types from your Rust code, it requires you to add a special annotation:

#[typeshare]
struct Color {
    r: u8,
    g: u8,
    b: u8,
}

In some cases, you may also need to add serde annotations:

#[typeshare]
#[serde(tag = "type", content = "content")]
pub enum BestNflTeam {
    KansasCity,
    Lies(String),
}

Adding Java Annotations

Java annotations can be added as follows:

#[typeshare(java(annotations = "@Getter"))]
pub enum Color {
    Red,
    Blue,
    Green,
}

Multiple annotations can be added like this:

#[typeshare(java(annotations = "
    @Getter
    @JsonAdapter(MyCustomAdapter.class)
"))]
pub enum Color {
    Red,
    Blue,
    Green,
}

Config

In most cases, config options can be passed via the command line. However, some options can only be added in a typeshare.toml file. Here is an example config file:

[java]
package = "com.typeshare.java"
namespace_class = true

[java.type_mappings]
Uuid = "java.util.UUID"

[java.header_comment]
type = "None"

[java.serializer]
type = "Gson"

Options

type-mappings

Rust types can be mapped to custom Java types.

[java.type_mappings]
Uuid = "java.util.UUID"

The above config results in the following Rust to Java conversion:

struct User {
    pub id: Uuid,
}
record User(
    java.util.UUID id
) {}
header-comment

A header comment can be added to each generated file.

[java.header_comment]
type = "Default" // Generated by typeshare-java <version>
// Or...
type = "None"
// Or...
type = "Custom"
comment = "This comment will be included at the top of each generated file"
package

The package name of the output.

package <package>; // Added to the top of output files
prefix

An optional prefix for type names.

#[typeshare]
struct Example {}

With prefix set to "Tada":

public record TadaExample() {}
namespace_class

Java only supports one top level file per class. We can get around this by wrapping classes in a namespace class. This will have the same name as the crate, converted to pascal case.

#[typeshare]
struct Inner {}

#[typeshare]
struct Outer {
    pub inner: Inner,
}

If the crate is named my_crate and namespace classes are enabled, the following Java code will be generated:

public class MyCrate {

    public record Inner() {}

    public record Outer(Inner inner) {}

}
serializer

Java has several serialization/deserialization packages. Depending on the Rust code you're working with, serializer specific code may need to be emitted. Currently, only None and Gson are supported. If None (default), then some data structures, such as algebraic enums, cannot be converted to Java code.

[java.serializer]
type = "None"
// Or...
type = "Gson"

indent

The indent type and size can be configured as follows:

[java.indent]
type = "Spaces"
size = 4

Dependencies

~10–20MB
~306K SLoC