Scala 3 provides a rich annotation system for controlling compiler behavior, optimization hints, API design, and metaprogramming capabilities.
class experimental extends StaticAnnotation
class alpha extends StaticAnnotation@experimental: Mark APIs that are experimental and may change@alpha: Mark APIs that are in alpha stage and unstableUsage:
@experimental
def newFeature(): String = "This API may change"
@alpha
class BetaClass:
def method(): Int = 42
// Usage requires explicit import
import scala.language.experimental.featureName
val result = newFeature() // Requires experimental importclass capability extends StaticAnnotationMark classes as capability classes for the capture checking system:
@capability
class FileReader
@capability
class NetworkAccess
def readFile(using FileReader): String = "file content"
def fetchData(using NetworkAccess): String = "network data"class constructorOnly extends StaticAnnotationRestrict annotation usage to constructors only:
@constructorOnly
class ValidatedInt(value: Int):
require(value >= 0, "Must be non-negative")
// Can only be used in constructors
class Person(@ValidatedInt age: Int, name: String)class targetName(name: String) extends StaticAnnotationOverride the name used for the method in compiled bytecode:
class Calculator:
@targetName("add")
def +(x: Int, y: Int): Int = x + y
@targetName("multiply")
def *(x: Int, y: Int): Int = x * y
// Bytecode will have methods named "add" and "multiply"
val calc = Calculator()
val sum = calc + (5, 3) // Calls "add" in bytecode
val product = calc * (4, 7) // Calls "multiply" in bytecodeclass static extends StaticAnnotationGenerate static methods in the bytecode:
object MathUtils:
@static
def square(x: Int): Int = x * x
@static
def cube(x: Double): Double = x * x * x
// Generates static methods in Java bytecode for Java interop
// Java code can call: MathUtils.square(5)class threadUnsafe extends StaticAnnotationMark code as thread-unsafe for optimization hints:
@threadUnsafe
class FastCounter:
private var count = 0
def increment(): Unit = count += 1
def get: Int = count
// Compiler can apply single-threaded optimizationsclass transparentTrait extends StaticAnnotationMark traits as transparent for better optimization:
@transparentTrait
trait Logging:
def log(msg: String): Unit = println(s"[LOG] $msg")
class Service extends Logging:
def process(): Unit =
log("Processing started")
// Processing logic
log("Processing completed")class unroll extends StaticAnnotationHint to the compiler to unroll loops or recursive calls:
@unroll
def factorial(n: Int): Int =
if n <= 1 then 1
else n * factorial(n - 1)
@unroll
def sumArray(arr: Array[Int]): Int =
var sum = 0
var i = 0
while i < arr.length do
sum += arr(i)
i += 1
sumclass publicInBinary extends StaticAnnotationForce methods to be public in bytecode even if they're private in Scala:
class Library:
@publicInBinary
private def internalMethod(): String = "internal"
def publicMethod(): String = internalMethod()
// internalMethod will be public in Java bytecode for framework accessclass init extends StaticAnnotationControl initialization order and behavior:
class Database:
@init
def initialize(): Unit =
// Critical initialization code
println("Database initialized")
def query(sql: String): String =
// Query implementation
s"Result for: $sql"trait MacroAnnotation extends StaticAnnotationBase trait for creating macro annotations that transform code at compile time:
import scala.annotation.MacroAnnotation
import scala.quoted.*
class toString extends MacroAnnotation:
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
import quotes.reflect.*
tree match
case ClassDef(name, constr, parents, self, body) =>
// Generate toString method
val toStringMethod = DefDef.copy(tree.asInstanceOf[DefDef])(
name = "toString",
paramss = List(List()),
tpt = TypeTree.of[String],
rhs = Some(Literal(StringConstant(s"Instance of $name")))
)
List(ClassDef.copy(tree.asInstanceOf[ClassDef])(
name, constr, parents, self, body :+ toStringMethod
))
case _ => List(tree)
// Usage
@toString
class Person(name: String, age: Int)
val person = Person("Alice", 30)
println(person.toString) // "Instance of Person"trait RefiningAnnotation extends StaticAnnotationBase trait for annotations that refine types:
import scala.annotation.RefiningAnnotation
class positive extends RefiningAnnotation
def process(@positive x: Int): Int =
require(x > 0)
x * 2
// The annotation refines the type to indicate positive integers
val result = process(5) // OK
// val invalid = process(-3) // Could be caught by static analysis@experimental
@targetName("advancedCalculation")
class Calculator:
@threadUnsafe
@unroll
private def fastCompute(n: Int): Long =
if n <= 1 then 1L
else n * fastCompute(n - 1)
@publicInBinary
@static
def compute(x: Int): Long = fastCompute(x)
// This class demonstrates multiple annotation types:
// - Experimental API that may change
// - Custom bytecode method name
// - Thread-unsafe optimization
// - Loop unrolling hint
// - Public binary visibility for private method
// - Static method generation// Define domain-specific annotations
class authenticated extends StaticAnnotation
class cached(timeoutSeconds: Int) extends StaticAnnotation
class rateLimit(requestsPerMinute: Int) extends StaticAnnotation
class UserService:
@authenticated
@cached(300) // 5 minute cache
def getUserProfile(userId: String): UserProfile =
// Expensive database operation
UserProfile(userId, "User Name")
@authenticated
@rateLimit(100) // 100 requests per minute
def updateUser(userId: String, data: UserData): Unit =
// Update user data
println(s"Updating user $userId")
// These annotations can be processed by frameworks or macros
// to automatically implement cross-cutting concernsclass min(value: Int) extends StaticAnnotation
class max(value: Int) extends StaticAnnotation
class email extends StaticAnnotation
class notNull extends StaticAnnotation
case class User(
@notNull @min(1) name: String,
@min(0) @max(150) age: Int,
@email email: String
)
// These annotations can be processed at compile time
// to generate validation logicclass deprecated(message: String, since: String) extends StaticAnnotation
class migration(from: String, to: String) extends StaticAnnotation
class APIService:
@deprecated("Use newMethod instead", "3.1.0")
def oldMethod(): String = "old implementation"
@migration(from = "oldMethod", to = "newMethod")
def newMethod(): String = "new implementation"
@experimental
def futureMethod(): String = "may change in future versions"
// Provides clear evolution path for APIs
val service = APIService()
val result1 = service.oldMethod() // Compiler warning about deprecation
val result2 = service.newMethod() // Current recommended approach
val result3 = service.futureMethod() // Requires experimental import// HTTP framework annotations
class GET(path: String) extends StaticAnnotation
class POST(path: String) extends StaticAnnotation
class PathParam(name: String) extends StaticAnnotation
class QueryParam(name: String) extends StaticAnnotation
class UserController:
@GET("/users/{id}")
def getUser(@PathParam("id") userId: String): User =
User(userId, "User Name")
@POST("/users")
def createUser(user: User): User =
// Create user logic
user
@GET("/users")
def searchUsers(@QueryParam("name") name: String): List[User] =
// Search logic
List(User("1", name))
// Database mapping annotations
class Table(name: String) extends StaticAnnotation
class Column(name: String) extends StaticAnnotation
class Id extends StaticAnnotation
@Table("users")
case class UserEntity(
@Id @Column("user_id") id: String,
@Column("full_name") name: String,
@Column("email_address") email: String
)The annotation system in Scala 3 provides powerful capabilities for controlling compiler behavior, enabling metaprogramming, and integrating with frameworks while maintaining type safety and compile-time verification.