Skip to main content
Version: dev

Scala

The Scala target emits Scala 3 source only. The fory-scala artifact still supports Scala 2.13 and Scala 3, but generated IDL source and macro derivation require Scala 3.

Output Layout

For package addressbook, Scala output is generated under:

  • <scala_out>/addressbook/
  • Type files: AddressBook.scala, Person.scala, Dog.scala, Cat.scala, Animal.scala
  • Schema module: AddressbookForyModule.scala

For schemas without a Scala package, the schema module name is derived from the source file stem, for example main.fdl generates MainForyModule.scala. Scala import graphs cannot mix default-package schemas with named Scala packages.

Type Generation

Messages outside compiler-detected construction cycles generate case classes:

import org.apache.fory.annotation.{ForyField, ForyStruct}
import org.apache.fory.scala.ForySerializer

@ForyStruct
final case class Person(
@ForyField(id = 1) name: String,
@ForyField(id = 3) email: Option[String],
@ForyField(id = 7) phones: List[Person.PhoneNumber],
@ForyField(id = 8) pet: Animal
) derives ForySerializer {
def toBytes(): Array[Byte] =
AddressbookForyModule.getFory.serialize(this)
}

object Person {
def fromBytes(bytes: Array[Byte]): Person =
AddressbookForyModule.getFory.deserialize(bytes).asInstanceOf[Person]
}

Messages in circular construction cycles generate normal classes with mutable serialized fields so reads can register the object before reading back-references:

import org.apache.fory.annotation.{ForyField, ForyStruct, Ref}
import org.apache.fory.scala.ForySerializer

@ForyStruct
final class Node() derives ForySerializer {
@ForyField(id = 1)
var id: String = ""

@Ref
@ForyField(id = 2)
var parent: Option[Node] = None
}

Enums generate Scala 3 enums with stable Fory IDs:

import org.apache.fory.annotation.ForyEnumId

enum PhoneType {
@ForyEnumId(0)
case Mobile

@ForyEnumId(1)
case Home

@ForyEnumId(2)
case Work
}

Unions generate Scala 3 ADT enums. Unknown(UnknownCase) is the Fory-provided forward-compatibility carrier marked with @ForyUnknownCase. It is omitted from the schema case table because the marker only selects the carrier and does not add a schema entry. Schema-defined cases use non-negative @ForyCase IDs. A typed union must have at least one non-Unknown case.

package addressbook

import org.apache.fory.annotation.{ForyCase, ForyUnion, ForyUnknownCase}
import org.apache.fory.scala.ForySerializer
import org.apache.fory.`type`.union.UnknownCase

@ForyUnion
enum Animal derives ForySerializer {
@ForyUnknownCase
case Unknown(value: UnknownCase)

@ForyCase(id = 0)
case Dog(value: _root_.addressbook.Dog)

@ForyCase(id = 1)
case Cat(value: _root_.addressbook.Cat)
}

Packaged Scala output keeps the schema case name and qualifies the payload type when both have the same simple name. If a target output mode cannot express a legal qualifier for a conflict, the compiler appends Case to the generated case name.

optional T fields generate Option[T]. Top-level message references use @Ref on the field or constructor parameter. Nested element/value references use type-use annotations such as List[Node @Ref].

Schema Module

Generated schema modules register schema serializers, enums, structs, and unions. The package-owned helper Fory instance uses ForyScala.builder().withXlang(true) with the schema module installed, so message toBytes/fromBytes helpers work without caller-managed Fory setup:

object AddressbookForyModule extends org.apache.fory.ForyModule {
private lazy val fory: ThreadSafeFory =
ForyScala.builder()
.withXlang(true)
.withRefTracking(true)
.withModule(this)
.buildThreadSafeFory()

private[addressbook] def getFory: ThreadSafeFory = fory

override def install(fory: Fory): Unit = {
ScalaSerializers.registerEnum(fory, classOf[Person.PhoneType], 101L)
ForySerializer.register(fory, classOf[Person.PhoneNumber], 102L)
ForySerializer.register(fory, classOf[Person], 100L)
ForySerializer.register(fory, classOf[Animal], 106L)
}
}

gRPC Service Companions

With --grpc, Scala emits one <ServiceName>Grpc.scala object per local service in the generated models' package. It exposes SERVICE_NAME, service and method descriptors, <ServiceName>ImplBase, and <ServiceName>Client. See Scala gRPC for RpcFuture, RpcIterator, grpc-java variants, and lifecycle guidance.