This is an automated email from the ASF dual-hosted git repository.

He-Pin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko-grpc.git


The following commit(s) were added to refs/heads/main by this push:
     new 09f3cebd feat: add Scala next cross-build coverage (#779)
09f3cebd is described below

commit 09f3cebdb1bc4b73b8a7461722a24c439259f10b
Author: He-Pin(kerr) <[email protected]>
AuthorDate: Mon Jul 6 05:25:02 2026 +0800

    feat: add Scala next cross-build coverage (#779)
    
    Motivation:\nPrepare the build for the next Scala 3 LTS lane while keeping 
published Scala artifacts on the current 2.13.x and 3.3.x release 
lines.\n\nModification:\nCentralize the next Scala version lookup for CI, keep 
published crossScalaVersions on release lanes, force the next lane only in 
build/test matrices, and fix Scala 3.8.4 compile, MiMa, and formatting 
issues.\n\nResult:\n2.13.x, 3.3.8, and 3.8.4 build/test coverage can run while 
MiMa and publishing remain on published Scal [...]
---
 .github/scripts/resolve-scala-version.sh           |  51 +++++
 .github/workflows/build-test.yml                   |  18 +-
 .github/workflows/publish-nightly.yml              |   9 +-
 .github/workflows/stage-release-candidate.yml      |   9 +-
 .../scala/org/apache/pekko/grpc/BenchRunner.scala  |   2 +-
 build.sbt                                          |  46 ++++-
 .../scala/org/apache/pekko/grpc/gen/Main.scala     |  73 ++++---
 .../pekko/grpc/interop/GrpcInteropTests.scala      |   4 +-
 .../pekko/grpc/interop/GrpcInteropSpec.scala       |   6 +-
 .../interop/PekkoHttpServerProviderScala.scala     |  27 ++-
 .../grpc/interop/app/PekkoHttpServerAppScala.scala |   8 +-
 .../scaladsl/NonBalancingIntegrationSpec.scala     |   2 +-
 .../apache/pekko/grpc/scaladsl/PowerApiSpec.scala  |   2 +-
 .../scaladsl/tools/MutableServiceDiscovery.scala   |   2 +-
 .../myapp/helloworld/ErrorReportingSpec.scala      |   7 +-
 .../myapp/helloworld/GreeterServiceSpec.scala      |  15 +-
 project/Common.scala                               |  22 +-
 project/Dependencies.scala                         |   5 +-
 project/ReflectiveCodeGen.scala                    | 224 +++++++++++++++++++--
 project/SbtMavenPlugin.scala                       |   2 +-
 project/VersionGenerator.scala                     |   2 +-
 .../apache/pekko/grpc/internal/TelemetrySpi.scala  |   1 +
 .../pekko/grpc/javadsl/ServerReflection.scala      |   3 +-
 .../apache/pekko/grpc/javadsl/ServiceHandler.scala |   4 +-
 .../org/apache/pekko/grpc/javadsl/WebHandler.scala |   2 +-
 .../org/apache/pekko/grpc/scaladsl/Grpc.scala      |   3 +-
 .../pekko/grpc/scaladsl/ServiceHandler.scala       |   2 +-
 .../apache/pekko/grpc/scaladsl/WebHandler.scala    |   2 +-
 .../pekko/grpc/scaladsl/headers/headers.scala      |   5 +
 .../internal/PekkoDiscoveryNameResolverSpec.scala  |   3 +-
 .../pekko/grpc/sbt/PackageMappingCompat.scala      |  34 ++++
 .../pekko/grpc/sbt/PackageMappingCompat.scala      |  31 +++
 .../apache/pekko/grpc/sbt/PekkoGrpcPlugin.scala    |  31 ++-
 .../src/main/scala/example/myapp/Main.scala        |  18 +-
 .../main/scala/example/myapp/helloworld/Main.scala |  86 ++++----
 .../src/main/scala/example/myapp/Main.scala        |  18 +-
 .../scala3/01-basic-client-server/build.sbt        |  10 +-
 .../sbt-test/scala3/02-scala3-sourcegen/build.sbt  |   8 +-
 .../src/sbt-test/scala3/03-sbt2-basic/build.sbt    |  12 +-
 .../scala/org/apache/pekko/grpc/scalapb/Main.scala |  30 +--
 40 files changed, 633 insertions(+), 206 deletions(-)

diff --git a/.github/scripts/resolve-scala-version.sh 
b/.github/scripts/resolve-scala-version.sh
new file mode 100644
index 00000000..d2846cd3
--- /dev/null
+++ b/.github/scripts/resolve-scala-version.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+set -euo pipefail
+
+scala_version="${1:?scala version is required}"
+repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+
+resolve_from_build() {
+  local pattern="$1"
+  local resolved
+  resolved="$(sed -n "$pattern" "$repo_root/project/Dependencies.scala")"
+  if [ -z "$resolved" ]; then
+    echo "Unable to resolve Scala version '$scala_version' from 
project/Dependencies.scala" >&2
+    exit 1
+  fi
+  printf '%s\n' "$resolved"
+}
+
+case "$scala_version" in
+  scala212)
+    resolve_from_build 's/.*val scala212 = "\(.*\)".*/\1/p'
+    ;;
+  2.13 | 2.13.x | scala213)
+    resolve_from_build 's/.*val scala213 = "\(.*\)".*/\1/p'
+    ;;
+  3.3 | 3.3.x | scala3)
+    resolve_from_build 's/.*val scala3 = "\(.*\)".*/\1/p'
+    ;;
+  next)
+    resolve_from_build 's/.*val scala3Next = "\(.*\)".*/\1/p'
+    ;;
+  *)
+    printf '%s\n' "$scala_version"
+    ;;
+esac
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index eb477de3..8466c409 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -85,7 +85,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        SCALA_VERSION: [2.13.18, 3.3.8]
+        SCALA_VERSION: [scala213, scala3, next]
         JAVA_VERSION: [17, 21]
     steps:
       - name: Checkout
@@ -107,9 +107,10 @@ jobs:
 
       - name: Compile and test for JDK ${{ matrix.JAVA_VERSION }}, Scala ${{ 
matrix.SCALA_VERSION }}
         run: |-
+          scala_version="$(bash .github/scripts/resolve-scala-version.sh "${{ 
matrix.SCALA_VERSION }}")"
           cp .jvmopts-ci .jvmopts
-          sbt ++${{ matrix.SCALA_VERSION }} test
-          sbt ++${{ matrix.SCALA_VERSION }}! codegen/test
+          sbt "++${scala_version}!" test
+          sbt "++${scala_version}!" codegen/test
 
   test-sbt:
     name: sbt scripted tests
@@ -121,7 +122,7 @@ jobs:
           - test-set: gen-scala-server
             scala-version: 2.13
           - test-set: scala3
-            scala-version: 3.3
+            scala-version: next
           - test-set: gen-java
             scala-version: 2.13
     steps:
@@ -151,7 +152,14 @@ jobs:
         uses: coursier/cache-action@95e5b1029b6b86e7bac033ee44a0697d8a527d2d # 
v8.1.1
 
       - name: Scripted ${{ matrix.test-set }}
-        run: cp .jvmopts-ci .jvmopts && sbt ++${{ matrix.scala-version }}.* 
"sbt-plugin/scripted ${{ matrix.test-set }}/*"
+        run: |-
+          cp .jvmopts-ci .jvmopts
+          if [ "${{ matrix.scala-version }}" = "next" ]; then
+            scala_version="$(bash .github/scripts/resolve-scala-version.sh 
"${{ matrix.scala-version }}")"
+            sbt "++${scala_version}!" "sbt-plugin/scripted ${{ matrix.test-set 
}}/*"
+          else
+            sbt "++${{ matrix.scala-version }}.*" "sbt-plugin/scripted ${{ 
matrix.test-set }}/*"
+          fi
 
   test-gradle:
     name: Gradle tests
diff --git a/.github/workflows/publish-nightly.yml 
b/.github/workflows/publish-nightly.yml
index b17119ca..08a8c210 100644
--- a/.github/workflows/publish-nightly.yml
+++ b/.github/workflows/publish-nightly.yml
@@ -36,11 +36,14 @@ jobs:
 
       - name: Publish
         run: |-
+          scala_212="$(bash .github/scripts/resolve-scala-version.sh scala212)"
+          scala_213="$(bash .github/scripts/resolve-scala-version.sh scala213)"
+          scala_3="$(bash .github/scripts/resolve-scala-version.sh scala3)"
           cp .jvmopts-ci .jvmopts
           sbt +publish
-          sbt ++2.12.21! maven-plugin/publish
-          sbt ++2.13.18! codegen/publish
-          sbt ++3.3.8! codegen/publish
+          sbt "++${scala_212}!" maven-plugin/publish
+          sbt "++${scala_213}!" codegen/publish
+          sbt "++${scala_3}!" codegen/publish
         env:
           NEXUS_USER: ${{ secrets.NEXUS_USER }}
           NEXUS_PW: ${{ secrets.NEXUS_PW }}
diff --git a/.github/workflows/stage-release-candidate.yml 
b/.github/workflows/stage-release-candidate.yml
index 102aded3..ca4bd3ea 100644
--- a/.github/workflows/stage-release-candidate.yml
+++ b/.github/workflows/stage-release-candidate.yml
@@ -226,12 +226,15 @@ jobs:
       - name: Build, sign and stage artifacts
         run: |-
           VERSION=$(echo $REF | sed -e "s/.\(.*\)-.*/\\1/")
+          scala_212="$(bash .github/scripts/resolve-scala-version.sh scala212)"
+          scala_213="$(bash .github/scripts/resolve-scala-version.sh scala213)"
+          scala_3="$(bash .github/scripts/resolve-scala-version.sh scala3)"
           echo "$PEKKO_GPG_SECRET_KEY" | gpg --batch --import --import-options 
import-show
 
           sbt "set ThisBuild / version := \"$VERSION\"; +publishSigned"
-          sbt "set ThisBuild / version := \"$VERSION\"; ++2.12.21! 
maven-plugin/publishSigned"
-          sbt "set ThisBuild / version := \"$VERSION\"; ++2.13.18! 
codegen/publishSigned"
-          sbt "set ThisBuild / version := \"$VERSION\"; ++3.3.8! 
codegen/publishSigned"
+          sbt "set ThisBuild / version := \"$VERSION\"; ++${scala_212}! 
maven-plugin/publishSigned"
+          sbt "set ThisBuild / version := \"$VERSION\"; ++${scala_213}! 
codegen/publishSigned"
+          sbt "set ThisBuild / version := \"$VERSION\"; ++${scala_3}! 
codegen/publishSigned"
           sbt "set ThisBuild / version := \"$VERSION\"; sonatypePrepare; set 
ThisBuild / version := \"$VERSION\"; sonatypeBundleUpload; sonatypeClose"
         env:
           REF: ${{ github.ref_name }}
diff --git a/benchmarks/src/main/scala/org/apache/pekko/grpc/BenchRunner.scala 
b/benchmarks/src/main/scala/org/apache/pekko/grpc/BenchRunner.scala
index 58d6c5d2..76f0272d 100644
--- a/benchmarks/src/main/scala/org/apache/pekko/grpc/BenchRunner.scala
+++ b/benchmarks/src/main/scala/org/apache/pekko/grpc/BenchRunner.scala
@@ -31,7 +31,7 @@ object BenchRunner {
     }
     // @formatter:on
 
-    val opts = new CommandLineOptions(args2: _*)
+    val opts = new CommandLineOptions(args2 *)
     val results = new Runner(opts).run()
 
     val report = results.asScala.map { (result: RunResult) =>
diff --git a/build.sbt b/build.sbt
index af477539..eba8a843 100644
--- a/build.sbt
+++ b/build.sbt
@@ -116,7 +116,12 @@ lazy val runtime = Project(id = "runtime", base = 
file("runtime"))
     ReflectiveCodeGen.generatedLanguages := Seq("Scala"),
     ReflectiveCodeGen.extraGenerators := Seq("ScalaMarshallersCodeGenerator"),
     PB.protocVersion := Dependencies.Versions.googleProtoc,
-    Test / PB.targets += (scalapb.gen() -> (Test / sourceManaged).value))
+    Test / PB.targets += {
+      val scalapbGenerator =
+        if (scalaBinaryVersion.value == "3") 
scalapb.gen(scalapb.GeneratorOption.Scala3Sources)
+        else scalapb.gen()
+      scalapbGenerator -> (Test / sourceManaged).value
+    })
   .enablePlugins(org.apache.pekko.grpc.build.ReflectiveCodeGen)
   .enablePlugins(ReproducibleBuildsPlugin)
 
@@ -162,6 +167,7 @@ lazy val mavenPlugin = Project(id = "maven-plugin", base = 
file("maven-plugin"))
   .settings(
     name := s"$pekkoPrefix-maven-plugin",
     crossPaths := false,
+    crossTarget := target.value / s"scala-${scalaVersion.value}",
     crossScalaVersions := Dependencies.Versions.CrossScalaForPlugin,
     scalaVersion := Dependencies.Versions.CrossScalaForPlugin.head)
   .dependsOn(codegen)
@@ -173,15 +179,21 @@ lazy val sbtPlugin = Project(id = "sbt-plugin", base = 
file("sbt-plugin"))
   .settings(Dependencies.sbtPlugin)
   .settings(
     name := s"$pekkoPrefix-sbt-plugin",
+    sbt.Keys.sbtPlugin :=
+      
Dependencies.Versions.CrossScalaForSbtPlugin.contains(scalaVersion.value),
     pluginCrossBuild / sbtVersion := {
-      scalaBinaryVersion.value match {
-        case "2.12" => "1.12.13"
-        case _      => "2.0.0"
+      if 
(!Dependencies.Versions.CrossScalaForSbtPlugin.contains(scalaVersion.value)) 
"1.12.13"
+      else {
+        scalaBinaryVersion.value match {
+          case "2.12" => "1.12.13"
+          case _      => "2.0.0"
+        }
       }
     },
     /** And for scripted tests: */
     scriptedSbt := (pluginCrossBuild / sbtVersion).value,
     scriptedLaunchOpts += ("-Dproject.version=" + version.value),
+    scriptedLaunchOpts += ("-Dpekko.grpc.scala3.next.version=" + 
Dependencies.Versions.scala3Next),
     scriptedLaunchOpts ++= sys.props.collect { case (k @ "sbt.ivy.home", v) => 
s"-D$k=$v" }.toSeq,
     scriptedDependencies := {
       val p1 = publishLocal.value
@@ -195,10 +207,25 @@ lazy val sbtPlugin = Project(id = "sbt-plugin", base = 
file("sbt-plugin"))
         case "2.12" => Seq("-Xsource:3")
         case _      => Seq.empty
       }
+    },
+    libraryDependencies := {
+      val dependencies = libraryDependencies.value
+      if 
(Dependencies.Versions.CrossScalaForSbtPlugin.contains(scalaVersion.value)) 
dependencies
+      else Seq.empty
+    },
+    Compile / sources := {
+      val sources0 = (Compile / sources).value
+      if 
(Dependencies.Versions.CrossScalaForSbtPlugin.contains(scalaVersion.value)) 
sources0
+      else Seq.empty
+    },
+    Test / sources := {
+      val sources0 = (Test / sources).value
+      if 
(Dependencies.Versions.CrossScalaForSbtPlugin.contains(scalaVersion.value)) 
sources0
+      else Seq.empty
     })
   .settings(
-    crossScalaVersions := Dependencies.Versions.CrossScalaForPlugin,
-    scalaVersion := Dependencies.Versions.CrossScalaForPlugin.head)
+    crossScalaVersions := Dependencies.Versions.CrossScalaForSbtPlugin,
+    scalaVersion := Dependencies.Versions.CrossScalaForSbtPlugin.head)
   .dependsOn(codegen)
 
 lazy val interopTests = Project(id = "interop-tests", base = 
file("interop-tests"))
@@ -220,8 +247,11 @@ lazy val interopTests = Project(id = "interop-tests", base 
= file("interop-tests
     ReflectiveCodeGen.generatedLanguages := Seq("Scala", "Java"),
     ReflectiveCodeGen.extraGenerators := Seq("ScalaMarshallersCodeGenerator"),
     ReflectiveCodeGen.codeGeneratorSettings ++= Seq("server_power_apis"),
-    // grpc 1.54.2 brings in extra unnecessary proto files that cause build 
issues
-    PB.generate / excludeFilter := new SimpleFileFilter(f => 
f.getAbsolutePath().contains("envoy")),
+    // grpc brings in extra unnecessary proto files that cause build issues or 
generated-code warnings
+    PB.generate / excludeFilter := new SimpleFileFilter(f => {
+      val path = f.getAbsolutePath.replace('\\', '/')
+      path.contains("envoy") || path.contains("grpc/reflection/v1alpha")
+    }),
     PB.protocVersion := Dependencies.Versions.googleProtoc,
     // We need to be able to publish locally in order for sbt interopt tests 
to work
     // however this sbt project should not be published to an actual repository
diff --git a/codegen/src/main/scala/org/apache/pekko/grpc/gen/Main.scala 
b/codegen/src/main/scala/org/apache/pekko/grpc/gen/Main.scala
index b50d4648..9b7ad4d6 100644
--- a/codegen/src/main/scala/org/apache/pekko/grpc/gen/Main.scala
+++ b/codegen/src/main/scala/org/apache/pekko/grpc/gen/Main.scala
@@ -16,59 +16,56 @@ package org.apache.pekko.grpc.gen
 import java.io.ByteArrayOutputStream
 import java.net.URLDecoder
 
-import scala.annotation.nowarn
-
 import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest
 import org.apache.pekko
 import pekko.grpc.gen.javadsl.{ JavaClientCodeGenerator, 
JavaInterfaceCodeGenerator, JavaServerCodeGenerator }
 import pekko.grpc.gen.scaladsl.{ ScalaClientCodeGenerator, 
ScalaServerCodeGenerator, ScalaTraitCodeGenerator }
 
 // This is the protoc plugin that the gradle plugin uses
-@nowarn("msg=deprecated")
-object Main extends App {
-  val inBytes: Array[Byte] = {
-    val baos = new ByteArrayOutputStream(math.max(64, System.in.available()))
-    val buffer = new Array[Byte](32 * 1024)
+object Main {
+  def main(args: Array[String]): Unit = {
+    val inBytes: Array[Byte] = {
+      val baos = new ByteArrayOutputStream(math.max(64, System.in.available()))
+      val buffer = new Array[Byte](32 * 1024)
 
-    var bytesRead = System.in.read(buffer)
-    while (bytesRead >= 0) {
-      baos.write(buffer, 0, bytesRead)
-      bytesRead = System.in.read(buffer)
+      var bytesRead = System.in.read(buffer)
+      while (bytesRead >= 0) {
+        baos.write(buffer, 0, bytesRead)
+        bytesRead = System.in.read(buffer)
+      }
+      baos.toByteArray
     }
-    baos.toByteArray
-  }
 
-  val req = CodeGeneratorRequest.parseFrom(inBytes)
-  val KeyValueRegex = """([^=]+)=(.*)""".r
-  val parameters = req.getParameter
-    .split(",")
-    .flatMap {
-      case KeyValueRegex(key, value) => Some((key.toLowerCase, value))
-      case _                         => None
-    }
-    .toMap
+    val req = CodeGeneratorRequest.parseFrom(inBytes)
+    val KeyValueRegex = """([^=]+)=(.*)""".r
+    val parameters = req.getParameter
+      .split(",")
+      .flatMap {
+        case KeyValueRegex(key, value) => Some((key.toLowerCase, value))
+        case _                         => None
+      }
+      .toMap
 
-  private val languageScala: Boolean = 
parameters.get("language").map(_.equalsIgnoreCase("scala")).getOrElse(false)
+    val languageScala: Boolean = 
parameters.get("language").map(_.equalsIgnoreCase("scala")).getOrElse(false)
 
-  private val generateClient: Boolean =
-    
parameters.get("generate_client").map(!_.equalsIgnoreCase("false")).getOrElse(true)
+    val generateClient: Boolean =
+      
parameters.get("generate_client").map(!_.equalsIgnoreCase("false")).getOrElse(true)
 
-  private val generateServer: Boolean =
-    
parameters.get("generate_server").map(!_.equalsIgnoreCase("false")).getOrElse(true)
+    val generateServer: Boolean =
+      
parameters.get("generate_server").map(!_.equalsIgnoreCase("false")).getOrElse(true)
 
-  private val extraGenerators: List[String] =
-    parameters.getOrElse("extra_generators", "").split(";").toList.filter(_ != 
"")
+    val extraGenerators: List[String] =
+      parameters.getOrElse("extra_generators", "").split(";").toList.filter(_ 
!= "")
 
-  // Prefer logfile_enc with fallback to logfile
-  private val logger: Logger =
-    parameters
-      .get("logfile_enc")
-      .map(URLDecoder.decode(_, "utf-8"))
-      .orElse(parameters.get("logfile"))
-      .map(new FileLogger(_))
-      .getOrElse(SilencedLogger)
+    // Prefer logfile_enc with fallback to logfile
+    val logger: Logger =
+      parameters
+        .get("logfile_enc")
+        .map(URLDecoder.decode(_, "utf-8"))
+        .orElse(parameters.get("logfile"))
+        .map(new FileLogger(_))
+        .getOrElse(SilencedLogger)
 
-  val out = {
     val codeGenerators =
       if (languageScala) {
         // Scala
diff --git 
a/interop-tests/src/main/scala/org/apache/pekko/grpc/interop/GrpcInteropTests.scala
 
b/interop-tests/src/main/scala/org/apache/pekko/grpc/interop/GrpcInteropTests.scala
index 45764504..7c29e77a 100644
--- 
a/interop-tests/src/main/scala/org/apache/pekko/grpc/interop/GrpcInteropTests.scala
+++ 
b/interop-tests/src/main/scala/org/apache/pekko/grpc/interop/GrpcInteropTests.scala
@@ -139,7 +139,7 @@ object IoGrpcJavaServerProvider extends GrpcServerProvider {
   val pendingCases =
     Set()
 
-  val server = IoGrpcServer
+  val server: GrpcServer[?] = IoGrpcServer
 }
 
 object IoGrpcJavaClientProvider extends GrpcClientProvider {
@@ -148,7 +148,7 @@ object IoGrpcJavaClientProvider extends GrpcClientProvider {
   val pendingCases =
     Set()
 
-  val client = IoGrpcClient
+  val client: GrpcClient = IoGrpcClient
 }
 
 trait PekkoHttpServerProvider extends GrpcServerProvider
diff --git 
a/interop-tests/src/test/scala/org/apache/pekko/grpc/interop/GrpcInteropSpec.scala
 
b/interop-tests/src/test/scala/org/apache/pekko/grpc/interop/GrpcInteropSpec.scala
index 577f1252..a9299c2d 100644
--- 
a/interop-tests/src/test/scala/org/apache/pekko/grpc/interop/GrpcInteropSpec.scala
+++ 
b/interop-tests/src/test/scala/org/apache/pekko/grpc/interop/GrpcInteropSpec.scala
@@ -79,7 +79,7 @@ object PekkoHttpServerProviderJava$ extends 
PekkoHttpServerProvider {
   val pendingCases =
     Set("custom_metadata")
 
-  val server = new PekkoGrpcServerJava((mat, sys) => {
+  val server: GrpcServer[?] = new PekkoGrpcServerJava((mat, sys) => {
     TestServiceHandlerFactory.create(new JavaTestServiceImpl(mat), sys)
   })
 }
@@ -87,13 +87,13 @@ object PekkoHttpServerProviderJava$ extends 
PekkoHttpServerProvider {
 class PekkoClientProviderScala(backend: String, testWithSslContext: Boolean) 
extends PekkoClientProvider {
   val label: String = s"pekko-grpc scala client tester $backend"
 
-  def client = PekkoGrpcClientScala(settings =>
+  def client: GrpcClient = PekkoGrpcClientScala(settings =>
     implicit sys => new PekkoGrpcScalaClientTester(settings, backend, 
testWithSslContext))
 }
 
 class PekkoClientProviderJava(backend: String, testWithSslContext: Boolean) 
extends PekkoClientProvider {
   val label: String = "pekko-grpc java client tester"
 
-  def client = new PekkoGrpcClientJava((settings, sys) =>
+  def client: GrpcClient = new PekkoGrpcClientJava((settings, sys) =>
     new PekkoGrpcJavaClientTester(settings, sys, backend, testWithSslContext))
 }
diff --git 
a/interop-tests/src/test/scala/org/apache/pekko/grpc/interop/PekkoHttpServerProviderScala.scala
 
b/interop-tests/src/test/scala/org/apache/pekko/grpc/interop/PekkoHttpServerProviderScala.scala
index 64ef1966..a7b9db38 100644
--- 
a/interop-tests/src/test/scala/org/apache/pekko/grpc/interop/PekkoHttpServerProviderScala.scala
+++ 
b/interop-tests/src/test/scala/org/apache/pekko/grpc/interop/PekkoHttpServerProviderScala.scala
@@ -16,15 +16,22 @@ package org.apache.pekko.grpc.interop
 import org.apache.pekko
 import pekko.NotUsed
 import pekko.actor.ActorSystem
-import pekko.grpc.GrpcProtocol
+import pekko.grpc.{ GrpcProtocol, ProtobufSerializer }
 import pekko.grpc.internal.{ GrpcEntityHelpers, GrpcProtocolNative, 
GrpcResponseHelpers, Identity }
+import pekko.http.scaladsl.marshalling.ToResponseMarshaller
 import pekko.http.scaladsl.model.headers.RawHeader
 import pekko.http.scaladsl.model.{ AttributeKeys, HttpEntity, HttpHeader, 
Trailer }
 import pekko.http.scaladsl.server.{ Directive0, Directives, Route }
+import pekko.http.scaladsl.unmarshalling.FromRequestUnmarshaller
 import pekko.stream.Materializer
 import pekko.stream.scaladsl.Source
 import io.grpc.Status
-import io.grpc.testing.integration.messages.{ SimpleRequest, 
StreamingOutputCallRequest }
+import io.grpc.testing.integration.messages.{
+  SimpleRequest,
+  SimpleResponse,
+  StreamingOutputCallRequest,
+  StreamingOutputCallResponse
+}
 import io.grpc.testing.integration.test.{ TestService, TestServiceHandler, 
TestServiceMarshallers }
 
 import scala.collection.immutable
@@ -63,7 +70,21 @@ object PekkoHttpServerProviderScala extends 
PekkoHttpServerProvider with Directi
     implicit val ec: ExecutionContext = mat.executionContext
     implicit val writer: GrpcProtocol.GrpcProtocolWriter = 
GrpcProtocolNative.newWriter(Identity)
 
-    import TestServiceMarshallers._
+    implicit val simpleRequestSerializer: ProtobufSerializer[SimpleRequest] =
+      TestService.Serializers.SimpleRequestSerializer
+    implicit val simpleResponseSerializer: ProtobufSerializer[SimpleResponse] =
+      TestService.Serializers.SimpleResponseSerializer
+    implicit val streamingOutputCallRequestSerializer: 
ProtobufSerializer[StreamingOutputCallRequest] =
+      TestService.Serializers.StreamingOutputCallRequestSerializer
+    implicit val streamingOutputCallResponseSerializer: 
ProtobufSerializer[StreamingOutputCallResponse] =
+      TestService.Serializers.StreamingOutputCallResponseSerializer
+    implicit val simpleRequestUnmarshaller: 
FromRequestUnmarshaller[SimpleRequest] =
+      TestServiceMarshallers.unmarshaller[SimpleRequest]
+    implicit val streamingOutputCallRequestUnmarshaller
+        : FromRequestUnmarshaller[Source[StreamingOutputCallRequest, NotUsed]] 
=
+      TestServiceMarshallers.toSourceUnmarshaller[StreamingOutputCallRequest]
+    implicit val simpleResponseMarshaller: 
ToResponseMarshaller[SimpleResponse] =
+      TestServiceMarshallers.marshaller[SimpleResponse]
 
     pathPrefix("UnaryCall") {
       entity(as[SimpleRequest]) { req =>
diff --git 
a/interop-tests/src/test/scala/org/apache/pekko/grpc/interop/app/PekkoHttpServerAppScala.scala
 
b/interop-tests/src/test/scala/org/apache/pekko/grpc/interop/app/PekkoHttpServerAppScala.scala
index 0901c698..7f0af18a 100644
--- 
a/interop-tests/src/test/scala/org/apache/pekko/grpc/interop/app/PekkoHttpServerAppScala.scala
+++ 
b/interop-tests/src/test/scala/org/apache/pekko/grpc/interop/app/PekkoHttpServerAppScala.scala
@@ -23,7 +23,9 @@ import 
org.apache.pekko.grpc.interop.PekkoHttpServerProviderScala
  *
  * You can start this app from sbt with 'interop-tests/test:reStart'
  */
-object PekkoHttpServerAppScala extends App {
-  val (sys, binding) = PekkoHttpServerProviderScala.server.start(Array())
-  sys.log.info(s"Bound to ${binding.localAddress}")
+object PekkoHttpServerAppScala {
+  def main(args: Array[String]): Unit = {
+    val (sys, binding) = PekkoHttpServerProviderScala.server.start(Array())
+    sys.log.info(s"Bound to ${binding.localAddress}")
+  }
 }
diff --git 
a/interop-tests/src/test/scala/org/apache/pekko/grpc/scaladsl/NonBalancingIntegrationSpec.scala
 
b/interop-tests/src/test/scala/org/apache/pekko/grpc/scaladsl/NonBalancingIntegrationSpec.scala
index 574e726e..1f1cf7bb 100644
--- 
a/interop-tests/src/test/scala/org/apache/pekko/grpc/scaladsl/NonBalancingIntegrationSpec.scala
+++ 
b/interop-tests/src/test/scala/org/apache/pekko/grpc/scaladsl/NonBalancingIntegrationSpec.scala
@@ -164,7 +164,7 @@ class NonBalancingIntegrationSpec(backend: String)
     "eventually fail when no valid endpoints are provided" in {
       // https://github.com/akka/akka-grpc/issues/1246
       if (backend == "pekko-http")
-        cancel("The Pekko HTTP backend doesn't fail when the persistent 
connection fails")
+        pending
 
       val discovery =
         new MutableServiceDiscovery(
diff --git 
a/interop-tests/src/test/scala/org/apache/pekko/grpc/scaladsl/PowerApiSpec.scala
 
b/interop-tests/src/test/scala/org/apache/pekko/grpc/scaladsl/PowerApiSpec.scala
index c1560c4a..f2f40a29 100644
--- 
a/interop-tests/src/test/scala/org/apache/pekko/grpc/scaladsl/PowerApiSpec.scala
+++ 
b/interop-tests/src/test/scala/org/apache/pekko/grpc/scaladsl/PowerApiSpec.scala
@@ -67,7 +67,7 @@ abstract class PowerApiSpec(backend: String)
   val server: Http.ServerBinding =
     Http().newServerAt("localhost", 0).bind(GreeterServicePowerApiHandler(new 
PowerGreeterServiceImpl())).futureValue
 
-  var client: GreeterServiceClient = _
+  var client: GreeterServiceClient = null
 
   after {
     if (client != null && !client.closed.isCompleted) {
diff --git 
a/interop-tests/src/test/scala/org/apache/pekko/grpc/scaladsl/tools/MutableServiceDiscovery.scala
 
b/interop-tests/src/test/scala/org/apache/pekko/grpc/scaladsl/tools/MutableServiceDiscovery.scala
index 1f088de6..97f1a245 100644
--- 
a/interop-tests/src/test/scala/org/apache/pekko/grpc/scaladsl/tools/MutableServiceDiscovery.scala
+++ 
b/interop-tests/src/test/scala/org/apache/pekko/grpc/scaladsl/tools/MutableServiceDiscovery.scala
@@ -29,7 +29,7 @@ import scala.concurrent.duration.FiniteDuration
  * An In-Memory ServiceDiscovery that only can lookup "greeter"
  */
 final class MutableServiceDiscovery(targets: List[InetSocketAddress]) extends 
ServiceDiscovery {
-  var services: Future[Resolved] = _
+  var services: Future[Resolved] = null
 
   setServices(targets)
 
diff --git 
a/plugin-tester-scala/src/test/scala/example/myapp/helloworld/ErrorReportingSpec.scala
 
b/plugin-tester-scala/src/test/scala/example/myapp/helloworld/ErrorReportingSpec.scala
index ae43a08d..f1eb708a 100644
--- 
a/plugin-tester-scala/src/test/scala/example/myapp/helloworld/ErrorReportingSpec.scala
+++ 
b/plugin-tester-scala/src/test/scala/example/myapp/helloworld/ErrorReportingSpec.scala
@@ -31,6 +31,7 @@ import org.scalatest.time.Span
 import org.scalatest.wordspec.AnyWordSpec
 import org.scalatestplus.junit.JUnitRunner
 
+import scala.annotation.nowarn
 import scala.concurrent.Await
 import scala.concurrent.duration._
 
@@ -45,7 +46,11 @@ class ErrorReportingSpec extends AnyWordSpec with Matchers 
with ScalaFutures wit
 
     val binding = Http()
       .newServerAt("127.0.0.1", 0)
-      .bind(GreeterServiceHandler(new 
GreeterServiceImpl())(system.asInstanceOf[ClassicActorSystemProvider]))
+      .bind {
+        @nowarn("msg=local val .* is never used")
+        implicit val classicSystem: ClassicActorSystemProvider = system
+        GreeterServiceHandler(new GreeterServiceImpl())
+      }
       .futureValue
 
     "respond with an 'unimplemented' gRPC error status when calling an unknown 
method" in {
diff --git 
a/plugin-tester-scala/src/test/scala/example/myapp/helloworld/GreeterServiceSpec.scala
 
b/plugin-tester-scala/src/test/scala/example/myapp/helloworld/GreeterServiceSpec.scala
index 676bdbc6..3431d459 100644
--- 
a/plugin-tester-scala/src/test/scala/example/myapp/helloworld/GreeterServiceSpec.scala
+++ 
b/plugin-tester-scala/src/test/scala/example/myapp/helloworld/GreeterServiceSpec.scala
@@ -27,6 +27,7 @@ import org.scalatest.time.Span
 import org.scalatest.wordspec.AnyWordSpecLike
 import org.scalatestplus.junit.JUnitRunner
 
+import scala.annotation.nowarn
 import scala.concurrent.{ Await, ExecutionContext }
 import scala.concurrent.duration._
 
@@ -51,11 +52,15 @@ class GreeterServiceSpec extends Matchers with 
AnyWordSpecLike with BeforeAndAft
 
   implicit val ec: ExecutionContext = clientSystem.dispatcher
 
-  val clients = Seq(8080, 8081).map { port =>
-    GreeterServiceClient(
-      GrpcClientSettings
-        .connectToServiceAt("127.0.0.1", 
port)(clientSystem.asInstanceOf[ClassicActorSystemProvider])
-        .withTls(false))(clientSystem.asInstanceOf[ClassicActorSystemProvider])
+  val clients = {
+    @nowarn("msg=local val .* is never used")
+    implicit val classicClientSystem: ClassicActorSystemProvider = clientSystem
+    Seq(8080, 8081).map { port =>
+      GreeterServiceClient(
+        GrpcClientSettings
+          .connectToServiceAt("127.0.0.1", port)
+          .withTls(false))
+    }
   }
 
   override def afterAll(): Unit = {
diff --git a/project/Common.scala b/project/Common.scala
index eb9a904c..b0529220 100644
--- a/project/Common.scala
+++ b/project/Common.scala
@@ -27,6 +27,15 @@ object Common extends AutoPlugin {
   private val consoleDisabledOptions = Seq("-Xfatal-warnings", 
"-Ywarn-unused", "-Ywarn-unused-import")
 
   val isScala3 = Def.setting(scalaBinaryVersion.value == "3")
+  val isScala38OrLater = 
Def.setting(CrossVersion.partialVersion(scalaVersion.value).exists {
+    case (3, minor) if minor >= 8 => true
+    case _                        => false
+  })
+
+  private val scala38WarningOptions = Seq(
+    "-Wconf:msg=Implicit parameters should be provided with a `using` 
clause:silent",
+    "-Wconf:msg=The trailing .* for eta-expansion is unnecessary:silent",
+    "-Wconf:msg=Usage of implicit .* is not accessible here:silent")
 
   override def globalSettings =
     Seq(
@@ -52,11 +61,12 @@ object Common extends AutoPlugin {
            "-Xfatal-warnings",
            "-Ywarn-unused",
            "-encoding",
-           "UTF-8")
+           "UTF-8") ++
+         (if (scalaVersion.value.startsWith("2.13.")) Seq("-Xsource:3") else 
Seq.empty)
        else
          Seq("-unchecked", "-deprecation", "-Werror", "-Wunused:imports", 
"-encoding", "UTF-8") ++
-         (if (CrossVersion.partialVersion(scalaVersion.value).exists(_._2 < 
9)) Seq("-Yfuture-lazy-vals")
-          else Seq.empty)),
+         (if (isScala38OrLater.value) scala38WarningOptions else Seq.empty) ++
+         (if (scalaVersion.value.startsWith("3.3.")) Seq("-Yfuture-lazy-vals") 
else Seq.empty)),
     Compile / scalacOptions ++=
       (if (!isScala3.value)
          Seq(
@@ -70,9 +80,15 @@ object Common extends AutoPlugin {
            // Generated code for methods/fields marked 'deprecated'
            "-Wconf:msg=Marked as deprecated in proto file:silent",
            "-Wconf:msg=unused import:silent",
+           "-Wconf:msg=transient key .* is excluded from the cache 
input:silent",
            "-Wconf:cat=feature:silent")),
     Compile / console / scalacOptions ~= 
(_.filterNot(consoleDisabledOptions.contains)),
     javacOptions ++= List("-Xlint:unchecked", "-Xlint:deprecation"),
+    javacOptions := {
+      val options = javacOptions.value
+      if (isScala38OrLater.value) options.filterNot(_.startsWith("-Xlint")) ++ 
Seq("-nowarn", "-Xlint:none")
+      else options
+    },
     Compile / compile / javacOptions ++= Seq("--release", "17"),
     Compile / compile / scalacOptions ++= Seq("-release", "17"),
     Test / compile / scalacOptions ++= Seq("-release", "17"),
diff --git a/project/Dependencies.scala b/project/Dependencies.scala
index 832f9a64..b23455c6 100644
--- a/project/Dependencies.scala
+++ b/project/Dependencies.scala
@@ -20,11 +20,12 @@ object Dependencies {
     val scala212 = "2.12.21"
     val scala213 = "2.13.18"
     val scala3 = "3.3.8"
-    val scala3_8 = "3.8.4"
+    val scala3Next = "3.8.4"
 
     // the order in the list is important because the head will be considered 
the default.
     val CrossScalaForLib = Seq(scala213, scala3)
-    val CrossScalaForPlugin = Seq(scala212, scala3_8)
+    val CrossScalaForPlugin = Seq(scala212, scala3)
+    val CrossScalaForSbtPlugin = Seq(scala212, scala3Next)
     val CrossScalaAll = Seq(scala212, scala213, scala3)
 
     // We don't force Pekko updates because downstream projects can upgrade
diff --git a/project/ReflectiveCodeGen.scala b/project/ReflectiveCodeGen.scala
index ed126bb0..80636113 100644
--- a/project/ReflectiveCodeGen.scala
+++ b/project/ReflectiveCodeGen.scala
@@ -10,6 +10,8 @@
 package org.apache.pekko.grpc.build
 
 import java.io.File
+import java.lang.invoke.{ MethodHandles, MethodType }
+import java.net.{ URL, URLClassLoader }
 import sbt._
 import sbt.Keys._
 import sbtprotoc.ProtocPlugin
@@ -24,16 +26,21 @@ import protocbridge.{ Artifact => BridgeArtifact }
 
 /** A plugin that allows to use a code generator compiled in one subproject to 
be used in a test project */
 object ReflectiveCodeGen extends AutoPlugin {
-  lazy val generatedLanguages = 
SettingKey[Seq[String]]("reflectiveGrpcGeneratedLanguages")
-  lazy val generatedSources = 
SettingKey[Seq[String]]("reflectiveGrpcGeneratedSources")
-  lazy val extraGenerators = 
SettingKey[Seq[String]]("reflectiveGrpcExtraGenerators")
-  lazy val codeGeneratorSettings = settingKey[Seq[String]]("Code generator 
settings")
-  lazy val protocOptions = settingKey[Seq[String]]("Protoc Options.")
+  lazy val generatedLanguages =
+    SettingKey[Seq[String]]("reflectiveGrpcGeneratedLanguages", "Generated 
languages").withRank(KeyRanks.Invisible)
+  lazy val generatedSources =
+    SettingKey[Seq[String]]("reflectiveGrpcGeneratedSources", "Generated 
sources").withRank(KeyRanks.Invisible)
+  lazy val extraGenerators =
+    SettingKey[Seq[String]]("reflectiveGrpcExtraGenerators", "Extra code 
generators").withRank(KeyRanks.Invisible)
+  lazy val codeGeneratorSettings =
+    SettingKey[Seq[String]]("codeGeneratorSettings", "Code generator 
settings").withRank(KeyRanks.Invisible)
+  lazy val protocOptions =
+    SettingKey[Seq[String]]("protocOptions", "Protoc 
options.").withRank(KeyRanks.Invisible)
 
   // needed to be able to override the PB.generate task reliably
   override lazy val requires = ProtocPlugin
 
-  override lazy val projectSettings: Seq[Def.Setting[?]] =
+  override lazy val projectSettings =
     inConfig(Compile)(
       Seq(
         PB.protocOptions := protocOptions.value,
@@ -72,16 +79,27 @@ object ReflectiveCodeGen extends AutoPlugin {
             }
           }
         }.value,
-        setCodeGenerator := loadAndSetGenerator(
-          // the magic sauce: use the output classpath from the the sbt-plugin 
project and instantiate generators from there
-          (ProjectRef(file("."), "sbt-plugin") / Compile / 
fullClasspath).value,
-          generatedLanguages.value,
-          generatedSources.value,
-          extraGenerators.value,
-          sourceManaged.value,
-          codeGeneratorSettings.value,
-          PB.targets.value.asInstanceOf[ListBuffer[Target]],
-          scalaBinaryVersion.value),
+        setCodeGenerator := Def.taskDyn {
+          // Scala 2.12 still uses the sbt-plugin/toolbox path. Library cross 
builds use codegen
+          // directly so forced 2.13/3.3 matrix runs do not compile the sbt 2 
plugin project.
+          val generatorProject =
+            if (scalaBinaryVersion.value == "2.12") "sbt-plugin"
+            else "codegen"
+
+          Def.task {
+            loadAndSetGenerator(
+              (ProjectRef(file("."), generatorProject) / Compile / 
fullClasspath).value,
+              generatedLanguages.value,
+              generatedSources.value,
+              extraGenerators.value,
+              sourceManaged.value,
+              codeGeneratorSettings.value ++ {
+                if (scalaBinaryVersion.value == "3") Seq("scala3_sources") 
else Seq.empty
+              },
+              PB.targets.value.asInstanceOf[ListBuffer[Target]],
+              scalaBinaryVersion.value)
+          }
+        }.value,
         (Compile / PB.protoSources) := PB.protoSources.value ++ Seq(
           PB.externalIncludePath.value,
           sourceDirectory.value / "proto"))) ++ Seq(
@@ -104,6 +122,19 @@ object ReflectiveCodeGen extends AutoPlugin {
       generatorSettings: Seq[String],
       targets: ListBuffer[Target],
       scalaBinaryVersion: String): Unit = {
+    if (scalaBinaryVersion != "2.12") {
+      loadAndSetGeneratorWithMethodHandles(
+        classpath,
+        languages0,
+        sources0,
+        extraGenerators0,
+        targetPath,
+        generatorSettings,
+        targets,
+        scalaBinaryVersion)
+      return
+    }
+
     val languages = languages0.mkString(", ")
     val sources = sources0.mkString(", ")
     val extraGenerators = extraGenerators0.mkString(", ")
@@ -146,6 +177,167 @@ object ReflectiveCodeGen extends AutoPlugin {
     targets ++= generators.asInstanceOf[Seq[Target]]
   }
 
+  private def loadAndSetGeneratorWithMethodHandles(
+      classpath: Classpath,
+      languages0: Seq[String],
+      sources0: Seq[String],
+      extraGenerators0: Seq[String],
+      targetPath: File,
+      generatorSettings: Seq[String],
+      targets: ListBuffer[Target],
+      scalaBinaryVersion: String): Unit = {
+    val generatorClasspath = classpath.map(_.data)
+    val codegenArtifact =
+      BridgeArtifact("org.apache.pekko", 
s"pekko-grpc-codegen_$scalaBinaryVersion", "0.0.0")
+
+    final case class GeneratorDefinition(name: String, className: String, 
suggestedDependencies: Seq[BridgeArtifact])
+
+    def codeGenerator(name: String): GeneratorDefinition = {
+      name match {
+        case "ScalaTraitCodeGenerator" =>
+          GeneratorDefinition(
+            "pekko-grpc-scaladsl-trait",
+            "org.apache.pekko.grpc.gen.scaladsl.ScalaTraitCodeGenerator$",
+            Seq.empty)
+        case "ScalaClientCodeGenerator" =>
+          GeneratorDefinition(
+            "pekko-grpc-scaladsl-client",
+            "org.apache.pekko.grpc.gen.scaladsl.ScalaClientCodeGenerator$",
+            Seq.empty)
+        case "ScalaServerCodeGenerator" =>
+          GeneratorDefinition(
+            "pekko-grpc-scaladsl-server",
+            "org.apache.pekko.grpc.gen.scaladsl.ScalaServerCodeGenerator$",
+            Seq.empty)
+        case "ScalaMarshallersCodeGenerator" =>
+          GeneratorDefinition(
+            "pekko-grpc-scaladsl-server-marshallers",
+            
"org.apache.pekko.grpc.gen.scaladsl.ScalaMarshallersCodeGenerator$",
+            Seq.empty)
+        case "JavaInterfaceCodeGenerator" =>
+          GeneratorDefinition(
+            "pekko-grpc-javadsl-interface",
+            "org.apache.pekko.grpc.gen.javadsl.JavaInterfaceCodeGenerator$",
+            Seq.empty)
+        case "JavaClientCodeGenerator" =>
+          GeneratorDefinition(
+            "pekko-grpc-javadsl-client",
+            "org.apache.pekko.grpc.gen.javadsl.JavaClientCodeGenerator$",
+            Seq.empty)
+        case "JavaServerCodeGenerator" =>
+          GeneratorDefinition(
+            "pekko-grpc-javadsl-server",
+            "org.apache.pekko.grpc.gen.javadsl.JavaServerCodeGenerator$",
+            Seq.empty)
+        case _ =>
+          GeneratorDefinition(name, 
s"org.apache.pekko.grpc.gen.scaladsl.$name$$", Seq.empty)
+      }
+    }
+
+    def sandboxedGenerator(definition: GeneratorDefinition): 
protocbridge.Generator =
+      protocbridge.SandboxedJvmGenerator.forResolver(
+        definition.name,
+        codegenArtifact,
+        definition.suggestedDependencies,
+        new MethodHandleProtocCodeGenerator(_, definition.className, 
generatorClasspath))
+
+    def scalaBaseGenerators: Seq[protocbridge.Generator] =
+      Seq(scalapb.gen.SandboxedGenerator, 
sandboxedGenerator(codeGenerator("ScalaTraitCodeGenerator")))
+    def javaBaseGenerators: Seq[protocbridge.Generator] =
+      Seq(PB.gens.java, 
sandboxedGenerator(codeGenerator("JavaInterfaceCodeGenerator")))
+
+    val baseGenerators = languages0 match {
+      case Seq("Scala") => scalaBaseGenerators
+      case Seq("Java")  => javaBaseGenerators
+      case Seq(_, _)    => scalaBaseGenerators ++ javaBaseGenerators
+    }
+
+    val stubGenerators = (for {
+      source <- sources0
+      language <- languages0
+    } yield (source, language) match {
+      case ("Client", "Scala") => codeGenerator("ScalaClientCodeGenerator")
+      case ("Server", "Scala") => codeGenerator("ScalaServerCodeGenerator")
+      case ("Client", "Java")  => codeGenerator("JavaClientCodeGenerator")
+      case ("Server", "Java")  => codeGenerator("JavaServerCodeGenerator")
+    }).distinct.map(sandboxedGenerator)
+
+    val generators =
+      (if (stubGenerators.nonEmpty) baseGenerators ++ stubGenerators else 
stubGenerators) ++
+      extraGenerators0.map(codeGenerator).map(sandboxedGenerator)
+
+    val protocJavaSettings =
+      Set("single_line_to_proto_string", "ascii_format_to_string", 
"retain_source_code_info")
+    val scalapbSettings =
+      Set("java_conversions", "flat_package", "single_line_to_proto_string", 
"ascii_format_to_string", "no_lenses",
+        "retain_source_code_info", "grpc", "scala3_sources")
+
+    val generatedTargets = generators.map { generator =>
+      val settings = generator match {
+        case PB.gens.java                                                      
     => generatorSettings.filter(protocJavaSettings)
+        case protocbridge.JvmGenerator("scala", _) | 
scalapb.gen.SandboxedGenerator =>
+          generatorSettings.filter(scalapbSettings)
+        case _ =>
+          generatorSettings
+      }
+      Target(generator, targetPath, settings)
+    }
+
+    targets.clear()
+    targets ++= generatedTargets
+  }
+
+  private final class MethodHandleProtocCodeGenerator(
+      classLoader: ClassLoader,
+      className: String,
+      classpath: Seq[File])
+      extends protocbridge.ProtocCodeGenerator {
+    private val childFirstClassLoader =
+      new ChildFirstClassLoader(classpath.map(_.toURI.toURL).toArray, 
classLoader)
+    private val lookup = MethodHandles.publicLookup()
+    private val moduleClass = childFirstClassLoader.loadClass(className)
+    private val module =
+      lookup.findStaticGetter(moduleClass, "MODULE$", 
moduleClass).invokeWithArguments()
+    private val loggerClass = 
childFirstClassLoader.loadClass("org.apache.pekko.grpc.gen.Logger")
+    private val loggerModuleClass = 
childFirstClassLoader.loadClass("org.apache.pekko.grpc.gen.SilencedLogger$")
+    private val logger =
+      lookup.findStaticGetter(loggerModuleClass, "MODULE$", 
loggerModuleClass).invokeWithArguments()
+    private val runMethod = lookup.findVirtual(
+      moduleClass,
+      "run",
+      MethodType.methodType(classOf[Array[Byte]], classOf[Array[Byte]], 
loggerClass))
+
+    override def run(request: Array[Byte]): Array[Byte] =
+      runMethod.invokeWithArguments(module, request.asInstanceOf[Object], 
logger).asInstanceOf[Array[Byte]]
+
+    override def toString = s"MethodHandleProtocCodeGenerator($className)"
+  }
+
+  private final class ChildFirstClassLoader(urls: Array[URL], parent: 
ClassLoader)
+      extends URLClassLoader(urls, parent) {
+    override def loadClass(name: String, resolve: Boolean): Class[?] =
+      getClassLoadingLock(name).synchronized {
+        val loaded: Class[?] = findLoadedClass(name)
+        val clazz: Class[?] =
+          if (loaded != null) loaded
+          else if (isPlatformClass(name)) 
ClassLoader.getPlatformClassLoader.loadClass(name)
+          else {
+            try findClass(name)
+            catch {
+              case _: ClassNotFoundException => super.loadClass(name, false)
+            }
+          }
+        if (resolve) resolveClass(clazz)
+        clazz
+      }
+
+    private def isPlatformClass(name: String): Boolean =
+      name.startsWith("java.") ||
+      name.startsWith("javax.") ||
+      name.startsWith("jdk.") ||
+      name.startsWith("sun.")
+  }
+
   lazy val generateTaskFromProtocPlugin: Def.Initialize[Task[Seq[File]]] =
     // lookup and return `PB.generate := ...` setting from ProtocPlugin
     ProtocPlugin.projectSettings
diff --git a/project/SbtMavenPlugin.scala b/project/SbtMavenPlugin.scala
index eb459034..04c45bd9 100644
--- a/project/SbtMavenPlugin.scala
+++ b/project/SbtMavenPlugin.scala
@@ -32,7 +32,7 @@ object SbtMavenPlugin extends AutoPlugin {
 
   import autoImport._
 
-  override lazy val projectSettings: Seq[Setting[?]] = 
inConfig(Compile)(unscopedSettings)
+  override lazy val projectSettings = inConfig(Compile)(unscopedSettings)
 
   lazy val unscopedSettings =
     Seq(
diff --git a/project/VersionGenerator.scala b/project/VersionGenerator.scala
index 42dfca49..399c38e6 100644
--- a/project/VersionGenerator.scala
+++ b/project/VersionGenerator.scala
@@ -17,7 +17,7 @@ import sbt._
  */
 object VersionGenerator {
 
-  lazy val settings: Seq[Setting[?]] = inConfig(Compile)(
+  lazy val settings = inConfig(Compile)(
     Seq(
       resourceGenerators += generateVersion(resourceManaged, _ / 
"pekko-grpc-version.conf",
         """|pekko.grpc.version = "%s"
diff --git 
a/runtime/src/main/scala/org/apache/pekko/grpc/internal/TelemetrySpi.scala 
b/runtime/src/main/scala/org/apache/pekko/grpc/internal/TelemetrySpi.scala
index 67d5fa15..055d3226 100644
--- a/runtime/src/main/scala/org/apache/pekko/grpc/internal/TelemetrySpi.scala
+++ b/runtime/src/main/scala/org/apache/pekko/grpc/internal/TelemetrySpi.scala
@@ -34,6 +34,7 @@ private[internal] class TelemetryExtensionImpl(val spi: 
TelemetrySpi) extends Ex
 /** INTERNAL API */
 @InternalStableApi
 object TelemetryExtension extends ExtensionId[TelemetryExtensionImpl] with 
ExtensionIdProvider {
+  @nowarn("msg=the inferred type changes")
   override def lookup = TelemetryExtension
   override def createExtension(system: ExtendedActorSystem) =
     new TelemetryExtensionImpl(TelemetrySpi(system))
diff --git 
a/runtime/src/main/scala/org/apache/pekko/grpc/javadsl/ServerReflection.scala 
b/runtime/src/main/scala/org/apache/pekko/grpc/javadsl/ServerReflection.scala
index 35b68a9e..e5de352c 100644
--- 
a/runtime/src/main/scala/org/apache/pekko/grpc/javadsl/ServerReflection.scala
+++ 
b/runtime/src/main/scala/org/apache/pekko/grpc/javadsl/ServerReflection.scala
@@ -32,8 +32,9 @@ object ServerReflection {
       objects: Collection[ServiceDescription],
       sys: ClassicActorSystemProvider): 
pekko.japi.function.Function[HttpRequest, CompletionStage[HttpResponse]] = {
     import scala.jdk.CollectionConverters._
+    implicit val system: ClassicActorSystemProvider = sys
     val delegate = ServerReflectionHandler.apply(
-      ServerReflectionImpl(objects.asScala.map(_.descriptor).toSeq, 
objects.asScala.map(_.name).toList))(sys)
+      ServerReflectionImpl(objects.asScala.map(_.descriptor).toSeq, 
objects.asScala.map(_.name).toList))
     import scala.jdk.FutureConverters._
     request =>
       delegate
diff --git 
a/runtime/src/main/scala/org/apache/pekko/grpc/javadsl/ServiceHandler.scala 
b/runtime/src/main/scala/org/apache/pekko/grpc/javadsl/ServiceHandler.scala
index a01303da..4a9bbdda 100644
--- a/runtime/src/main/scala/org/apache/pekko/grpc/javadsl/ServiceHandler.scala
+++ b/runtime/src/main/scala/org/apache/pekko/grpc/javadsl/ServiceHandler.scala
@@ -46,7 +46,7 @@ object ServiceHandler {
   @varargs
   def concatOrNotFound(handlers: JFunction[HttpRequest, 
CompletionStage[HttpResponse]]*)
       : JFunction[HttpRequest, CompletionStage[HttpResponse]] =
-    handler(handlers: _*)
+    handler(handlers *)
 
   /**
    * Creates a `HttpRequest` to `HttpResponse` handler for gRPC services that 
can be used in
@@ -56,7 +56,7 @@ object ServiceHandler {
   @varargs
   def handler(handlers: JFunction[HttpRequest, CompletionStage[HttpResponse]]*)
       : JFunction[HttpRequest, CompletionStage[HttpResponse]] = {
-    val servicesHandler = concat(handlers: _*)
+    val servicesHandler = concat(handlers *)
     (req: HttpRequest) => servicesHandler(req)
   }
 
diff --git 
a/runtime/src/main/scala/org/apache/pekko/grpc/javadsl/WebHandler.scala 
b/runtime/src/main/scala/org/apache/pekko/grpc/javadsl/WebHandler.scala
index 627c4df5..7f4d748d 100644
--- a/runtime/src/main/scala/org/apache/pekko/grpc/javadsl/WebHandler.scala
+++ b/runtime/src/main/scala/org/apache/pekko/grpc/javadsl/WebHandler.scala
@@ -77,7 +77,7 @@ object WebHandler {
       corsSettings: CorsSettings): JFunction[HttpRequest, 
CompletionStage[HttpResponse]] = {
     import scala.jdk.CollectionConverters._
 
-    val servicesHandler = concatOrNotFound(handlers.asScala.toList: _*)
+    val servicesHandler = concatOrNotFound(handlers.asScala.toList *)
     val servicesRoute = 
RouteAdapter(MarshallingDirectives.handleWith(servicesHandler.apply(_)))
     val handler = asyncHandler(CorsDirectives.cors(corsSettings, () => 
servicesRoute), as, mat)
     (req: HttpRequest) =>
diff --git a/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/Grpc.scala 
b/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/Grpc.scala
index 63bf2a7c..24aabe40 100644
--- a/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/Grpc.scala
+++ b/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/Grpc.scala
@@ -39,7 +39,8 @@ private[grpc] final class GrpcImpl(system: 
ExtendedActorSystem) extends Extensio
           .map(channel =>
             channel.close().recover {
               case e =>
-                val log = Logging(system, getClass)(LogSource.fromClass)
+                implicit val logSource: LogSource[Class[?]] = 
LogSource.fromClass
+                val log = Logging(system, getClass)
                 log.warning("Failed to gracefully close {}, proceeding with 
shutdown anyway. {}", channel, e)
                 Done
             }))
diff --git 
a/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/ServiceHandler.scala 
b/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/ServiceHandler.scala
index c816321a..b020474a 100644
--- a/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/ServiceHandler.scala
+++ b/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/ServiceHandler.scala
@@ -38,7 +38,7 @@ object ServiceHandler {
 
   def concatOrNotFound(
       handlers: PartialFunction[HttpRequest, Future[HttpResponse]]*): 
HttpRequest => Future[HttpResponse] =
-    concat(handlers: _*).orElse { case _ => notFound }
+    concat(handlers *).orElse { case _ => notFound }
 
   def concat(handlers: PartialFunction[HttpRequest, Future[HttpResponse]]*)
       : PartialFunction[HttpRequest, Future[HttpResponse]] =
diff --git 
a/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/WebHandler.scala 
b/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/WebHandler.scala
index 97240dda..6391b51d 100644
--- a/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/WebHandler.scala
+++ b/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/WebHandler.scala
@@ -63,7 +63,7 @@ object WebHandler {
       implicit as: ClassicActorSystemProvider,
       corsSettings: CorsSettings = defaultCorsSettings): HttpRequest => 
Future[HttpResponse] = {
     implicit val system: ActorSystem = as.classicSystem
-    val servicesHandler = ServiceHandler.concat(handlers: _*)
+    val servicesHandler = ServiceHandler.concat(handlers *)
     Route.toFunction(cors(corsSettings) {
       handleWith(servicesHandler)
     })
diff --git 
a/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/headers/headers.scala 
b/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/headers/headers.scala
index eab0e002..7c894dfa 100644
--- 
a/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/headers/headers.scala
+++ 
b/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/headers/headers.scala
@@ -21,6 +21,7 @@ import pekko.http.javadsl.{ model => jm }
 
 import scala.collection.compat.immutable.ArraySeq
 import scala.collection.immutable
+import scala.annotation.nowarn
 import scala.util.Try
 
 @ApiMayChange
@@ -28,6 +29,7 @@ final class `Message-Accept-Encoding`(override val value: 
String)
     extends ModeledCustomHeader[`Message-Accept-Encoding`] {
   override def renderInRequests = true
   override def renderInResponses = true
+  @nowarn("msg=the inferred type changes")
   override val companion = `Message-Accept-Encoding`
 
   lazy val values: Array[String] = value.split(',')
@@ -55,6 +57,7 @@ object `Message-Accept-Encoding` extends 
ModeledCustomHeaderCompanion[`Message-A
 final class `Message-Encoding`(encoding: String) extends 
ModeledCustomHeader[`Message-Encoding`] {
   override def renderInRequests = true
   override def renderInResponses = true
+  @nowarn("msg=the inferred type changes")
   override val companion = `Message-Encoding`
   override def value: String = encoding
 }
@@ -79,6 +82,7 @@ object `Message-Encoding` extends 
ModeledCustomHeaderCompanion[`Message-Encoding
 final class `Status`(code: Int) extends ModeledCustomHeader[`Status`] {
   override def renderInRequests = false
   override def renderInResponses = true
+  @nowarn("msg=the inferred type changes")
   override val companion = `Status`
 
   override def value() = code.toString
@@ -98,6 +102,7 @@ object `Status` extends 
ModeledCustomHeaderCompanion[`Status`] {
 final class `Status-Message`(val unencodedValue: String) extends 
ModeledCustomHeader[`Status-Message`] {
   override def renderInRequests = false
   override def renderInResponses = true
+  @nowarn("msg=the inferred type changes")
   override val companion = `Status-Message`
   override def value() = PercentEncoding.Encoder.encode(unencodedValue)
 }
diff --git 
a/runtime/src/test/scala/org/apache/pekko/grpc/internal/PekkoDiscoveryNameResolverSpec.scala
 
b/runtime/src/test/scala/org/apache/pekko/grpc/internal/PekkoDiscoveryNameResolverSpec.scala
index bd339996..bfd3dc92 100644
--- 
a/runtime/src/test/scala/org/apache/pekko/grpc/internal/PekkoDiscoveryNameResolverSpec.scala
+++ 
b/runtime/src/test/scala/org/apache/pekko/grpc/internal/PekkoDiscoveryNameResolverSpec.scala
@@ -53,8 +53,7 @@ class PekkoDiscoveryNameResolverSpec
     }
 
     "support serving a static host/port" in {
-      // Unfortunately it needs to be an actually resolvable address...
-      val host = "akka.io"
+      val host = "localhost"
       val port = 4040
       val resolver = 
PekkoDiscoveryNameResolver(GrpcClientSettings.connectToServiceAt(host, port))
       val probe = new NameResolverListenerProbe()
diff --git 
a/sbt-plugin/src/main/scala-2/org/apache/pekko/grpc/sbt/PackageMappingCompat.scala
 
b/sbt-plugin/src/main/scala-2/org/apache/pekko/grpc/sbt/PackageMappingCompat.scala
new file mode 100644
index 00000000..6bce92d7
--- /dev/null
+++ 
b/sbt-plugin/src/main/scala-2/org/apache/pekko/grpc/sbt/PackageMappingCompat.scala
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.pekko.grpc.sbt
+
+import scala.annotation.nowarn
+
+import java.io.File
+
+import xsbti.FileConverter
+
+private[sbt] object PackageMappingCompat {
+  type PackageMapping = (File, String)
+
+  def packageMapping(file: File, path: String, @nowarn("msg=is never used") 
fileConverter: FileConverter)
+      : PackageMapping =
+    file -> path
+}
diff --git 
a/sbt-plugin/src/main/scala-3/org/apache/pekko/grpc/sbt/PackageMappingCompat.scala
 
b/sbt-plugin/src/main/scala-3/org/apache/pekko/grpc/sbt/PackageMappingCompat.scala
new file mode 100644
index 00000000..c58982d1
--- /dev/null
+++ 
b/sbt-plugin/src/main/scala-3/org/apache/pekko/grpc/sbt/PackageMappingCompat.scala
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.pekko.grpc.sbt
+
+import java.io.File
+
+import xsbti.{ FileConverter, HashedVirtualFileRef }
+
+private[sbt] object PackageMappingCompat {
+  type PackageMapping = (HashedVirtualFileRef, String)
+
+  def packageMapping(file: File, path: String, fileConverter: FileConverter): 
PackageMapping =
+    fileConverter.toVirtualFile(file.toPath) -> path
+}
diff --git 
a/sbt-plugin/src/main/scala/org/apache/pekko/grpc/sbt/PekkoGrpcPlugin.scala 
b/sbt-plugin/src/main/scala/org/apache/pekko/grpc/sbt/PekkoGrpcPlugin.scala
index 3d615020..754a40c6 100644
--- a/sbt-plugin/src/main/scala/org/apache/pekko/grpc/sbt/PekkoGrpcPlugin.scala
+++ b/sbt-plugin/src/main/scala/org/apache/pekko/grpc/sbt/PekkoGrpcPlugin.scala
@@ -120,23 +120,17 @@ object PekkoGrpcPlugin extends AutoPlugin {
         packageBin / mappings := {
           val existingMappings = (packageBin / mappings).value
           val unpackedFiles = PB.unpackDependencies.value.files
+          val converter = fileConverter.value
           val mappingsToAdd =
-            
unpackedFiles.pair(Path.relativeTo(Seq(PB.externalSourcePath.value, 
PB.externalIncludePath.value)))
-          @scala.annotation.tailrec
-          def withoutDuplicates(soFar: List[(File, String)], seen: 
Set[String], toAdd: Seq[(File, String)])
-              : Seq[(File, String)] = {
-            toAdd.headOption match {
-              case Some((file, string)) =>
-                if (seen.contains(string)) {
-                  withoutDuplicates(soFar, seen, toAdd.tail)
-                } else {
-                  withoutDuplicates((file, string) :: soFar, seen + string, 
toAdd.tail)
-                }
-              case None =>
-                soFar
-            }
-          }
-          withoutDuplicates(existingMappings.toList, 
existingMappings.map(_._2).toSet, mappingsToAdd)
+            unpackedFiles
+              .pair(Path.relativeTo(Seq(PB.externalSourcePath.value, 
PB.externalIncludePath.value)))
+              .map { case (file, path) => 
PackageMappingCompat.packageMapping(file, path, converter) }
+          (existingMappings.toList ++ 
mappingsToAdd).foldLeft((List.empty[PackageMappingCompat.PackageMapping],
+            Set.empty[String])) {
+            case ((soFar, seen), mapping @ (_, path)) =>
+              if (seen(path)) (soFar, seen)
+              else (mapping :: soFar, seen + path)
+          }._1.reverse
         },
         unmanagedResourceDirectories ++= (PB.recompile / 
unmanagedResourceDirectories).value,
         Defaults.ConfigZero / watchSources ++= Def.uncached {
@@ -154,7 +148,7 @@ object PekkoGrpcPlugin extends AutoPlugin {
         PB.targets ++=
           targetsFor(
             (pekkoGrpcCodeGeneratorSettings / target).value,
-            pekkoGrpcCodeGeneratorSettings.value,
+            pekkoGrpcCodeGeneratorSettings.value ++ 
scala3SourcesSettings.value,
             pekkoGrpcGenerators.value),
         PB.protoSources += sourceDirectory.value / "proto"))
 
@@ -176,6 +170,9 @@ object PekkoGrpcPlugin extends AutoPlugin {
         })
     }
 
+  private def scala3SourcesSettings: Def.Initialize[Seq[String]] =
+    Def.setting(if (scalaBinaryVersion.value == "3") Seq("scala3_sources") 
else Seq.empty)
+
   // creates a seq of generator and per generator settings
   def generatorsFor(
       stubs: Seq[PekkoGrpc.GeneratedSource],
diff --git 
a/sbt-plugin/src/sbt-test/gen-scala-server/02-multiple-services/src/main/scala/example/myapp/Main.scala
 
b/sbt-plugin/src/sbt-test/gen-scala-server/02-multiple-services/src/main/scala/example/myapp/Main.scala
index 8afa412c..33f33175 100644
--- 
a/sbt-plugin/src/sbt-test/gen-scala-server/02-multiple-services/src/main/scala/example/myapp/Main.scala
+++ 
b/sbt-plugin/src/sbt-test/gen-scala-server/02-multiple-services/src/main/scala/example/myapp/Main.scala
@@ -24,16 +24,18 @@ import example.myapp.echo.grpc.EchoServiceHandler
 import example.myapp.helloworld.GreeterServiceImpl
 import example.myapp.helloworld.grpc.GreeterServiceHandler
 
-object Main extends App {
-  implicit val system: ActorSystem = ActorSystem()
+object Main {
+  def main(args: Array[String]): Unit = {
+    implicit val system: ActorSystem = ActorSystem()
 
-  val echoHandler = EchoServiceHandler.partial(new EchoServiceImpl)
-  val greeterHandler = GreeterServiceHandler.partial(new GreeterServiceImpl)
-  val serviceHandler = ServiceHandler.concatOrNotFound(echoHandler, 
greeterHandler)
+    val echoHandler = EchoServiceHandler.partial(new EchoServiceImpl)
+    val greeterHandler = GreeterServiceHandler.partial(new GreeterServiceImpl)
+    val serviceHandler = ServiceHandler.concatOrNotFound(echoHandler, 
greeterHandler)
 
-  Http().newServerAt("localhost", 8443)
-    .enableHttps(serverHttpContext())
-    .bind(serviceHandler)
+    Http().newServerAt("localhost", 8443)
+      .enableHttps(serverHttpContext())
+      .bind(serviceHandler)
+  }
 
   private def serverHttpContext() = {
     // never put passwords into code!
diff --git 
a/sbt-plugin/src/sbt-test/gen-scala-server/04-server-reflection/src/main/scala/example/myapp/helloworld/Main.scala
 
b/sbt-plugin/src/sbt-test/gen-scala-server/04-server-reflection/src/main/scala/example/myapp/helloworld/Main.scala
index 431445df..3f47be9e 100644
--- 
a/sbt-plugin/src/sbt-test/gen-scala-server/04-server-reflection/src/main/scala/example/myapp/helloworld/Main.scala
+++ 
b/sbt-plugin/src/sbt-test/gen-scala-server/04-server-reflection/src/main/scala/example/myapp/helloworld/Main.scala
@@ -29,48 +29,50 @@ import example.myapp.helloworld.grpc._
 
 //#server-reflection
 
-object Main extends App {
-  val conf = ConfigFactory
-    .parseString("pekko.http.server.enable-http2 = on")
-    .withFallback(ConfigFactory.defaultApplication())
-  implicit val sys: ActorSystem = ActorSystem("HelloWorld", conf)
-
-  implicit val ec: ExecutionContext = sys.dispatcher
-
-  // #server-reflection
-  // Create service handler with a fallback to a Server Reflection handler.
-  // `.withServerReflection` is a convenience method that contacts a partial
-  // function of the provided service with a reflection handler for that
-  // same service.
-  val greeter: HttpRequest => Future[HttpResponse] =
-    GreeterServiceHandler.withServerReflection(new GreeterServiceImpl())
-
-  // Bind service handler servers to localhost:8080
-  val binding = Http().newServerAt("127.0.0.1", 8080)
-    .bind(greeter)
-  // #server-reflection
-
-  // report successful binding
-  binding.foreach { binding =>
-    println(s"gRPC server bound to: ${binding.localAddress}")
+object Main {
+  def main(args: Array[String]): Unit = {
+    val conf = ConfigFactory
+      .parseString("pekko.http.server.enable-http2 = on")
+      .withFallback(ConfigFactory.defaultApplication())
+    implicit val sys: ActorSystem = ActorSystem("HelloWorld", conf)
+
+    implicit val ec: ExecutionContext = sys.dispatcher
+
+    // #server-reflection
+    // Create service handler with a fallback to a Server Reflection handler.
+    // `.withServerReflection` is a convenience method that contacts a partial
+    // function of the provided service with a reflection handler for that
+    // same service.
+    val greeter: HttpRequest => Future[HttpResponse] =
+      GreeterServiceHandler.withServerReflection(new GreeterServiceImpl())
+
+    // Bind service handler servers to localhost:8080
+    val binding = Http().newServerAt("127.0.0.1", 8080)
+      .bind(greeter)
+    // #server-reflection
+
+    // report successful binding
+    binding.foreach { binding =>
+      println(s"gRPC server bound to: ${binding.localAddress}")
+    }
+
+    // #server-reflection-manual-concat
+    // Create service handlers
+    val greeterPartial: PartialFunction[HttpRequest, Future[HttpResponse]] =
+      GreeterServiceHandler.partial(new GreeterServiceImpl(), 
"greeting-prefix")
+    val echoPartial: PartialFunction[HttpRequest, Future[HttpResponse]] =
+      EchoServiceHandler.partial(new EchoServiceImpl())
+    // Create the reflection handler for multiple services
+    val reflection =
+      ServerReflection.partial(List(GreeterService, EchoService))
+
+    // Concatenate the partial functions into a single handler
+    val handler =
+      ServiceHandler.concatOrNotFound(
+        greeterPartial,
+        echoPartial,
+        reflection)
+    // #server-reflection-manual-concat
   }
 
-  // #server-reflection-manual-concat
-  // Create service handlers
-  val greeterPartial: PartialFunction[HttpRequest, Future[HttpResponse]] =
-    GreeterServiceHandler.partial(new GreeterServiceImpl(), "greeting-prefix")
-  val echoPartial: PartialFunction[HttpRequest, Future[HttpResponse]] =
-    EchoServiceHandler.partial(new EchoServiceImpl())
-  // Create the reflection handler for multiple services
-  val reflection =
-    ServerReflection.partial(List(GreeterService, EchoService))
-
-  // Concatenate the partial functions into a single handler
-  val handler =
-    ServiceHandler.concatOrNotFound(
-      greeterPartial,
-      echoPartial,
-      reflection)
-  // #server-reflection-manual-concat
-
 }
diff --git 
a/sbt-plugin/src/sbt-test/gen-scala-server/10-scalapb-validate/src/main/scala/example/myapp/Main.scala
 
b/sbt-plugin/src/sbt-test/gen-scala-server/10-scalapb-validate/src/main/scala/example/myapp/Main.scala
index 94210ea8..fa1894e8 100644
--- 
a/sbt-plugin/src/sbt-test/gen-scala-server/10-scalapb-validate/src/main/scala/example/myapp/Main.scala
+++ 
b/sbt-plugin/src/sbt-test/gen-scala-server/10-scalapb-validate/src/main/scala/example/myapp/Main.scala
@@ -14,16 +14,18 @@ import scalapb.validate._
 
 import example.myapp.helloworld.grpc.HelloRequest
 
-object Main extends App {
+object Main {
+  def main(args: Array[String]): Unit = {
 
-  Try(HelloRequest("valid")) match {
-    case Success(_) => // expected
-    case Failure(e) => throw new RuntimeException("unexpected violations for 
\"valid\"", e)
-  }
+    Try(HelloRequest("valid")) match {
+      case Success(_) => // expected
+      case Failure(e) => throw new RuntimeException("unexpected violations for 
\"valid\"", e)
+    }
 
-  Try(HelloRequest("ko")) match {
-    case Success(_) => throw new RuntimeException("unexpected success for 
\"ko\"")
-    case Failure(e) => // expected
+    Try(HelloRequest("ko")) match {
+      case Success(_) => throw new RuntimeException("unexpected success for 
\"ko\"")
+      case Failure(e) => // expected
+    }
   }
 
 }
diff --git a/sbt-plugin/src/sbt-test/scala3/01-basic-client-server/build.sbt 
b/sbt-plugin/src/sbt-test/scala3/01-basic-client-server/build.sbt
index 14a49e49..b4411f23 100644
--- a/sbt-plugin/src/sbt-test/scala3/01-basic-client-server/build.sbt
+++ b/sbt-plugin/src/sbt-test/scala3/01-basic-client-server/build.sbt
@@ -7,9 +7,15 @@
  * This file is part of the Apache Pekko project, which was derived from Akka.
  */
 
-scalaVersion := "3.3.8"
+scalaVersion := sys.props.getOrElse(
+  "pekko.grpc.scala3.next.version",
+  sys.error("pekko.grpc.scala3.next.version must be provided by 
scriptedLaunchOpts"))
 
-scalacOptions += "-Xfatal-warnings"
+scalacOptions ++= Seq(
+  "-Werror",
+  "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s",
+  "-Wconf:msg=Ignoring \\[this\\] qualifier:s",
+  "-Wconf:msg=`_` is deprecated for wildcard arguments of types:s")
 
 enablePlugins(PekkoGrpcPlugin)
 
diff --git a/sbt-plugin/src/sbt-test/scala3/02-scala3-sourcegen/build.sbt 
b/sbt-plugin/src/sbt-test/scala3/02-scala3-sourcegen/build.sbt
index 6bacc82e..5c6cd01a 100644
--- a/sbt-plugin/src/sbt-test/scala3/02-scala3-sourcegen/build.sbt
+++ b/sbt-plugin/src/sbt-test/scala3/02-scala3-sourcegen/build.sbt
@@ -7,9 +7,13 @@
  * This file is part of the Apache Pekko project, which was derived from Akka.
  */
 
-scalaVersion := "3.3.8"
+scalaVersion := sys.props.getOrElse(
+  "pekko.grpc.scala3.next.version",
+  sys.error("pekko.grpc.scala3.next.version must be provided by 
scriptedLaunchOpts"))
 
-scalacOptions += "-Xfatal-warnings"
+scalacOptions ++= Seq(
+  "-Werror",
+  "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s")
 
 enablePlugins(PekkoGrpcPlugin)
 
diff --git a/sbt-plugin/src/sbt-test/scala3/03-sbt2-basic/build.sbt 
b/sbt-plugin/src/sbt-test/scala3/03-sbt2-basic/build.sbt
index 8509801e..e4134ab0 100644
--- a/sbt-plugin/src/sbt-test/scala3/03-sbt2-basic/build.sbt
+++ b/sbt-plugin/src/sbt-test/scala3/03-sbt2-basic/build.sbt
@@ -7,8 +7,14 @@
  * This file is part of the Apache Pekko project, which was derived from Akka.
  */
 
-// Verify that the pekko-grpc sbt plugin cross-builds correctly for sbt 1.x 
and sbt 2.x.
-// When run in the Scala 3 build pass (+scripted), scriptedSbt is set to 2.x.y.
-scalaVersion := "3.3.7"
+// Verify that the pekko-grpc sbt plugin cross-builds correctly for sbt 2.x.
+scalaVersion := sys.props.getOrElse(
+  "pekko.grpc.scala3.next.version",
+  sys.error("pekko.grpc.scala3.next.version must be provided by 
scriptedLaunchOpts"))
+
+scalacOptions ++= Seq(
+  "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s",
+  "-Wconf:msg=Ignoring \\[this\\] qualifier:s",
+  "-Wconf:msg=`_` is deprecated for wildcard arguments of types:s")
 
 enablePlugins(PekkoGrpcPlugin)
diff --git 
a/scalapb-protoc-plugin/src/main/scala/org/apache/pekko/grpc/scalapb/Main.scala 
b/scalapb-protoc-plugin/src/main/scala/org/apache/pekko/grpc/scalapb/Main.scala
index fcbf6132..21032602 100644
--- 
a/scalapb-protoc-plugin/src/main/scala/org/apache/pekko/grpc/scalapb/Main.scala
+++ 
b/scalapb-protoc-plugin/src/main/scala/org/apache/pekko/grpc/scalapb/Main.scala
@@ -17,21 +17,23 @@ import java.io.ByteArrayOutputStream
 
 import scalapb.ScalaPbCodeGenerator
 
-object Main extends App {
-  val inBytes: Array[Byte] = {
-    val baos = new ByteArrayOutputStream(math.max(64, System.in.available()))
-    val buffer = Array.ofDim[Byte](32 * 1024)
-
-    var bytesRead = System.in.read(buffer)
-    while (bytesRead >= 0) {
-      baos.write(buffer, 0, bytesRead)
-      bytesRead = System.in.read(buffer)
+object Main {
+  def main(args: Array[String]): Unit = {
+    val inBytes: Array[Byte] = {
+      val baos = new ByteArrayOutputStream(math.max(64, System.in.available()))
+      val buffer = Array.ofDim[Byte](32 * 1024)
+
+      var bytesRead = System.in.read(buffer)
+      while (bytesRead >= 0) {
+        baos.write(buffer, 0, bytesRead)
+        bytesRead = System.in.read(buffer)
+      }
+      baos.toByteArray
     }
-    baos.toByteArray
-  }
 
-  val outBytes = ScalaPbCodeGenerator.run(inBytes)
+    val outBytes = ScalaPbCodeGenerator.run(inBytes)
 
-  System.out.write(outBytes)
-  System.out.flush()
+    System.out.write(outBytes)
+    System.out.flush()
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to