I have made my scala 3 terse notes here http://gibbons.org.uk/terse-scala3-notes-2021.

Now time to migrate my small crud rest app, how hard can it be? Well state of play in June 2021 is that loads of scala 2 libraries use macros, and they have not moved yet.

https://docs.scala-lang.org/scala3/guides/migration/compatibility-classpath.html So, I read this, and decided to just give it a go with only scala 3 libraries….

So, this is what I had to change: Change all your dependencies so no longer %% to match your scala version, instead append _2.13 or _3 or whatever maven central says is the supported version.


"com.typesafe.scala-logging" % "scala-logging_2.13"   % ScalaLoggingVersion,

Instead bringing in

"ch.qos.logback"             % "logback-classic"      % LogbackVersion,
"ch.qos.logback"             % "logback-core"         % LogbackVersion,
"org.slf4j"                  % "slf4j-api"            % "1.7.30",

Scala test has version 3 for scala 3.0.0, so import it using:

“org.scalatest” % “scalatest_3” % ScalaTestVersion % Test,

Ditch scala mock.

Circe does have scala 3 versions:

val jsonCircelibraryDependencies = Seq(
  "io.circe" % "circe-core_3",
  "io.circe" % "circe-generic_3",
  "io.circe" % "circe-parser_3"
).map(_ % CirceVersion)

Ditch all those little helpers, which don’t actually give you much

//    "io.chrisdavenport"          %% "log4cats-core"   % Log4CatsVersion,
//    "io.chrisdavenport"          %% "log4cats-slf4j"  % Log4CatsVersion,
//    "io.circe"                   % "circe-config_2.13"    % CirceConfigVersion

http4s, is available, but rho-swagger isn’t, so drop swagger for now. Hand crank it if you need swagger.

val Http4sVersion = "1.0.0-M23"
val KowMsCatsDependencies = Seq(
  "com.zaxxer"      % "HikariCP" % HikariCpVersion,
  "org.http4s"      % "http4s-blaze-server_3" % Http4sVersion,
  "org.http4s"      % "http4s-blaze-client_3" % Http4sVersion,
  "org.http4s"      % "http4s-circe_3"        % Http4sVersion,
  "org.http4s"      % "http4s-dsl_3"          % Http4sVersion,
//    "org.http4s"      % "rho-swagger_2.13"         % RhoSwaggerVersion
) ++lamCommonDependencies

and fs2 is available

val fs2Version = "3.0.4"
"co.fs2" % "fs2-core_3" % fs2Version

On the first day I tried this scala logging was not available for scala3, but the next day it was. So things are moving.

Code Stuff

Along the way it didn’t like lambda parameters out of (), so below I had to parenthesise req: Request[IO]

GET / "swagger-ui" |>> { (req: Request[IO]) => fetchResource(swaggerDir + "/index.html", req, blocker) }

And then 3 hours later….

circe in scala 3 mostly doesnt work. Http4s has trouple, intellij is complaining about TASTY versions. Its a massive mess.

So, for now, sadly, its too early to port projects if you are using any of the normally available libraries. And I’ve wasted my Sunday. grrr.

Try again

Later on, I started from a simple scala3 project and added in the blase dependenceies. IntelliJ could not cope, and blaze did not work.

Try again again

I reverted the code, then repeated the steps above. Then I created circe encoders and decoders for all of my classes received and sent from http4s, and stuck them in a seperate object which I imported only as needed.

It worked! I have my microservice back in scala 3 - no swagger, but I can live without it.

ie Circe with http4s circe was not working without encoders and decoders for each nested case class of the json.

object RenderAsJson :
  import io.circe._
  import io.circe.generic.semiauto._
  import io.circe.syntax.EncoderOps

  implicit val  myMapPointEncoder : Encoder[ClientMapPoint] = deriveEncoder
  implicit val  myMapPolyEncoder : Encoder[ClientMapPoly] = deriveEncoder
  implicit val  myMapTerrainEncoder : Encoder[ClientTerrain] = deriveEncoder
  implicit val  myMapEncoder : Encoder[ClientMapDesignDto] = deriveEncoder

  implicit val  myMapPointDecoder : Decoder[ClientMapPoint] = deriveDecoder
  implicit val  myMapPolyDecoder : Decoder[ClientMapPoly] = deriveDecoder
  implicit val  myMapTerrainDecoder : Decoder[ClientTerrain] = deriveDecoder
  implicit val  myMapDecoder : Decoder[ClientMapDesignDto] = deriveDecoder