Scala tooling
- Drive the build with sbt commands. Projects use sbt 2, which differs from sbt 1:
sbtconnects to a background server by default; don't add--client. The server keeps the environment of its first start;sbt shutdownto reset it.- multiple commands must be one quoted argument:
sbt "clean ; compile ; test" testonly runs failed, new or affected tests;testFullruns allsbt runstarts the app in a separate JVM; killing sbt doesn't stop it, kill that JVM.unknown event: sbt/clientJobmeans the sbt runner is too old.build.sbtis Scala 3: no postfix calls, slash syntax only (Test / compile). Bare settings apply to all subprojects (noThisBuild /). Tasks are cached: wrap side-effecting custom tasks inDef.uncached.- outputs are in
target/out/jvm/scala-<version>/<project>/
- resolve dependency versions from
https://repo1.maven.org/maven2/<group-with-slashes>/<artifact>/maven-metadata.xml, including the suffix (com/softwaremill/ox/core_3; sbt plugins:_sbt2_3). Take the latest stable<version>;<release>may be a prerelease (RC,M<n>,SNAP,alpha,beta,NIGHTLY). NEVER usesearch.maven.org/solrsearch(stale by months). Find unknown coordinates viahttps://index.scala-lang.org/api/autocomplete?q=<name>. - before committing, ALWAYS run
sbt scalafmtAll - the project MUST compile with zero warnings. Ensure
build.sbtincludes-Wunused:all,-Wvalue-discard,-Wnonunit-statementinscalacOptions. Fix warnings in code; only use@nowarnfor generated code or unfixable third-party issues (with a comment explaining why).
Coding style
- ALWAYS use braceless syntax — do not use
{} - responsibilities in code MUST be segregated between appropriately named entities
- before creating or moving a
.scalafile, decide its package, filename, and each top-level declaration's visibility (narrowest the call sites allow). Read Code Organization and Visibility when adding packages/modules or widening visibility. - each function MUST handle exactly one concern — either a single logical operation, or a short orchestration of named steps.
- use Ox
.pipe/.tap(import ox.*) to drop single-usevals that only feed the next line; keep a namedvalwhen the name documents intent. - tests MUST be targeted — each test covers exactly one scenario. No overlapping or redundant tests.
- every public function, val, and given MUST have an explicit return type — this prevents accidental signature drift during refactors.
Performance
- NEVER materialize unbounded data in memory — stream with
Flowor page. - virtual threads are never preempted: long CPU-bound work needs
computeIntensiveorcede()— see CPU-intensive work.
Direct-style Scala
- in Tapir, wire logic with
.handle/.handleSecurity/.handleSuccess— NEVER.serverLogic*(it needs an effect wrapper). - ALWAYS use Ox for threading, channels, and async coordination — not raw threads, queues, locks, or lifecycle flags. See Use Ox primitives.
- decide the owning scope before writing concurrent code: prefer a local
supervisedper request/job; return the fork's result, never an object owning running forks. Read Scope propagation before taking(using Ox)/(using ResourceScope)or forking from a factory. - a fork blocked reading a subprocess pipe, stdin, a file, or other classic
java.iostream is NOT ended by scope cancellation — shutdown deadlocks. BEFORE driving a subprocess or reading a blocking external stream (socket, SSE), read Subprocesses and External Streams.
Functional programming
Worked examples: Domain Modeling.
- use pure functions, immutable data, higher-order functions, ADTs. NEVER use shared mutable state.
vardeclarations MUST be inside methods (e.g. processing loops), never as class fields. Class-levelvars break reasoning and testability. The sole exception is mutable state encapsulated by an OxActor, which serialises every invocation onto a single thread — the actor is what makes the field safe to hold (see Concurrency and Inter-Thread Communication). Use only immutable collections (Map,Set,List) — nevermutable.Map,mutable.Set,mutable.Buffer(mutable collections are fine in test fakes).- model state as an immutable case class; transitions are pure
state => statefunctions; confine thevarto the smallest scope. - APIs MUST be lawful: pass
Clock,Random, and UUID generation explicitly (parameter or constructor) — never call them inside methods. - use type-safe domain modelling: design types so invalid data CANNOT be
constructed — enums, ADTs, opaque types,
Optionfor absence (nevernull), smart constructors returningEither(notrequire) - wrap
String,Int,Long, andBooleandomain values in opaque types or enums — NEVER use raw primitives for domain concepts (identifiers, quantities, configuration values). - replace
Booleanparameters and return values with two-case enums. - recoverable failures: return
Either[E, T]— never throw,require, or baretry/catch. Exceptions only for unrecoverable errors that end the current request/message. - define typed error hierarchies — NEVER use stringly-typed errors.
Use-Case Guide
BEFORE writing code that uses Tapir, Ox, or sttp, or touches a topic listed
below, read the relevant chapter(s) in full. Follow every API and > Required /
> Important callout exactly — do NOT substitute another approach. Read the
chapter file from this skill's directory, or with a raw HTTP GET from the base
URL below — never via a tool that summarises the page (summaries drop callouts
and exact API calls).
https://raw.githubusercontent.com/virtuslab/scala-skill/refs/heads/master/direct-style-scala/skills/direct-style-scala/
Application Structure
- New Project Setup — sbt + Ox skeleton,
build.sbt,scalacOptions,OxApp.Simple, adopt-tapir - Code Organization and Visibility — top-level visibility, file naming, package shadowing, sbt/Scalafix boundaries
- Domain Modeling — immutable state, lawful APIs (
Clock, UUID), opaque types, smart constructors, enums vsBoolean - Resource Management —
useInScope,useCloseableInScope,resourceScope,ResourceScope, release order - Background Processes —
OxApp,forkDiscard/forkUserDiscard,forever/sleeploops, orderly shutdown - Type-Safe Configuration — PureConfig
derives ConfigReader, env overrides,Sensitive, load-time validation - Compile-Time Dependency Injection — MacWire
autowire,autowireMembersOf,wireList - Concurrency and Inter-Thread Communication — Flow
mapPar/merge/mapStateful,par, channels,Actor,AtomicReference,computeIntensive/cede,(using Ox)scope propagation - Subprocesses and External Streams — subprocess/socket/SSE reader forks, destroy before join, process-tree teardown,
abandonOnInterruptReads, pipe back-pressure
Error Handling
- Error Handling —
FailADT,either/.ok(),transactEither,.catching, nesting rules - Error Output Customisation — JSON error responses,
Fail→ status code mapping,failOutput,defaultHandlers - Decode Failure Handling —
DefaultDecodeFailureHandler,onDecodeFailureNextEndpoint, custom messages,hideEndpointsWithAuth
HTTP & Endpoints
- Authentication —
secureEndpoint[T],AuthTokenOps[T],Auth[T],handleSecurity - HTTP Server Configuration —
Identityeffect, security headers, CORS, SPA static files, request cancellation,NettySyncServer - Version API —
sbt-buildinfo,BuildInfogit hash endpoint - Compile-Time OpenAPI Generation — build-time OpenAPI YAML,
EndpointsForDocs,@maingenerator, sbt task - SOAP with scalaxb — XSD codegen, SOAP envelopes, Tapir XML codecs,
SOAPActionrouting, SOAP faults - JSON Request and Response Bodies — jsoniter codecs, list-body codecs, enums as strings via
withDiscriminatorFieldName(None), opaque types in DTOs - Endpoint Inputs — path/query/header
PlainCodecs, opaque-type ids,Codec.derivedEnumeration
Data & Integration
- SQL Persistence — Magnum,
@Table,DbCodec,Repo/TableInfo,sqlinterpolation, Flyway, HikariCP - Sending Emails —
EmailScheduler, SMTP/Mailgun/dummy senders, templates, batch sending - Kafka Streaming —
KafkaFlow.subscribe,mapPar,KafkaDrain, offset commits, transactional produce-and-commit, graceful shutdown
Testing & Observability
- Testing HTTP Endpoints —
TapirSyncStubInterpreter,SttpClientInterpreter, public and secured endpoints in-process - OpenTelemetry Observability — SDK auto-config, Tapir/sttp instrumentation, custom metrics,
PropagatingVirtualThreadFactory, MDC correlation


