Akka gRPC Quickstart with Java
Akka gRPC is a toolkit for building streaming gRPC servers and clients on top of Akka Streams. This guide will get you started building gRPC based systems with Java. If you prefer to use Akka gRPC with Scala, switch to the Akka gRPC Quickstart with Scala guide.
After trying this example the Akka gRPC documentation is a good next step to continue learning more about Akka gRPC.
Downloading the example
The Hello World example for Scala is a zipped project that includes a distribution of Maven, Gradle and sbt. You can choose any of these build tools. You can run it on Linux, MacOS, or Windows. The only prerequisite is Java 17 or later, and an installation of maven.
Download and unzip the example:
- Download the project zip file.
- Extract the zip file to a convenient location.
- On Linux and OSX systems, open a terminal and use the command
unzip akka-grpc-quickstart-java.zip
. - On Windows, use a tool such as File Explorer to extract the project.
Running the example
To run Hello World:
On OSX/Linux use sbt
to start sbt in the instructions below, on Windows sbt.bat
. On OSX/Linux use ./gradlew
to start Gradle in the instructions below, on Windows ./gradlew.bat
. For Gradle you have to make the build tool executable with chmod u+x ./gradlew
-
In a console, change directories to the top level of the unzipped project.
For example, if you used the default project name, akka-grpc-quickstart, and extracted the project to your root directory, from the root directory, enter:
cd akka-grpc-quickstart
-
Compile the project by entering:
- Maven
-
mvn compile
- Gradle
-
./gradlew compileJava
- sbt
-
sbt compile
sbtMavenGradle downloads project dependencies, generates gRPC classes from protobuf, and compiles.
-
Run the server:
- Maven
-
mvn compile dependency:properties exec:exec@server
- Gradle
-
./gradlew runServer
- sbt
-
sbt "runMain com.example.helloworld.GreeterServer"
The
exec:exec@server
execution is defined in the Mavenpom.xml
build definition. TherunServer
task is defined inbuild.gradle
. sbtMavenGradle runs thecom.example.helloworld.GreeterServer
main class that starts the gRPC server.The output should include something like:
gRPC server bound to: /127.0.0.1:8080
-
Run the client, open another console window and enter:
- Maven
-
mvn compile dependency:properties exec:exec@client
- Gradle
-
./gradlew runClient
- sbt
-
sbt "runMain com.example.helloworld.GreeterClient"
The
exec:exec@client
execution is defined in the Mavenpom.xml
build definition. TherunClient
task is defined inbuild.gradle
. sbtMavenGradle runs thecom.example.helloworld.GreeterClient
main class that starts the gRPC client.The output should include something like:
Performing request: Alice Performing request: Bob HelloReply(Hello, Bob) HelloReply(Hello, Alice)
Congratulations, you just ran your first Akka gRPC server and client. Now take a look at what happened under the covers.
You can end the programs with ctrl-c
.
What Hello World does
As you saw in the console output, the example outputs several greetings. Let’s take at the code and what happens at runtime.
Server
First, the GreeterServer
main class creates an akka.actor.typed.ActorSystem
, a container in which Actors, Akka Streams and Akka HTTP run. Next, it defines a function from HttpRequest
to CompletionStage<HttpResponse>
using the GreeterServiceImpl
. This function handles gRPC requests in the HTTP/2 with TLS server that is bound to port 8080 in this example.
source
import akka.actor.typed.ActorSystem;
import akka.actor.typed.javadsl.Behaviors;
import akka.http.javadsl.*;
import akka.http.javadsl.common.SSLContextFactory;
import akka.http.javadsl.model.HttpRequest;
import akka.http.javadsl.model.HttpResponse;
import akka.japi.function.Function;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.concurrent.CompletionStage;
public class GreeterServer {
public static void main(String[] args) throws Exception {
// important to enable HTTP/2 in ActorSystem's config
Config conf = ConfigFactory.parseString("akka.http.server.preview.enable-http2 = on")
.withFallback(ConfigFactory.load());
ActorSystem<Void> system = ActorSystem.create(Behaviors.empty(), "GreeterServer", conf);
new GreeterServer(system).run();
}
final ActorSystem<?> system;
public GreeterServer(ActorSystem<?> system) {
this.system = system;
}
public CompletionStage<ServerBinding> run() throws Exception {
Function<HttpRequest, CompletionStage<HttpResponse>> service =
GreeterServiceHandlerFactory.create(
new GreeterServiceImpl(system),
system);
HttpsConnectionContext httpsConnectionContext = ConnectionContext.httpsServer(SSLContextFactory.createSSLContextFromPem(
// Note: filesystem paths, not classpath
Paths.get("src/main/resources/certs/server1.pem"),
Paths.get("src/main/resources/certs/server1.key")
));
CompletionStage<ServerBinding> bound =
Http.get(system)
.newServerAt("127.0.0.1", 8080)
.enableHttps(httpsConnectionContext)
.bind(service);
bound.thenAccept(binding ->
System.out.println("gRPC server bound to: " + binding.localAddress())
);
return bound;
}
}
GreeterServiceImpl
is our implementation of the gRPC service, but first we must define the interface of the service in the protobuf file src/main/proto/helloworld.proto
:
sourcesyntax = "proto3";
option java_multiple_files = true;
option java_package = "com.example.helloworld";
option java_outer_classname = "HelloWorldProto";
// The greeting service definition.
service GreeterService {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
When compiling the project several things are generated from the proto definition. You can find the generated files in target/scala-2.13/akka-grpc/main/
target/generated-sources/
build/generated/source/proto/main/
if you are curious.
For the server the following classes are generated:
- Message classes, such as
HelloRequest
andHelloReply
GreeterService
interface of the serviceGreeterServiceHandler
utility to create theHttpRequest
toHttpResponse
function from theGreeterServiceImpl
The part that we have to implement on the server side is the GreeterServiceImpl
which implements the generated GreeterService
interface. It is this implementation that is bound to the HTTP
server via the GreeterServiceHandler
and it looks like this:
source
import akka.NotUsed;
import akka.japi.Pair;
import akka.actor.typed.ActorSystem;
import akka.stream.javadsl.BroadcastHub;
import akka.stream.javadsl.Keep;
import akka.stream.javadsl.MergeHub;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
class GreeterServiceImpl implements GreeterService {
final ActorSystem<?> system;
public GreeterServiceImpl(ActorSystem<?> system) {
this.system = system;
}
@Override
public CompletionStage<HelloReply> sayHello(HelloRequest request) {
return CompletableFuture.completedFuture(
HelloReply.newBuilder()
.setMessage("Hello, " + request.getName())
.build()
);
}
}
Client
In this example we have the client in the same project as the server. That is common for testing purposes but for real usage you or another team would have a separate project (different service) that is using the client and doesn’t implement the server side of the service. Between such projects you would only share the proto file (by copying it).
From the same proto file that was used on the server side classes are generated for the client:
- Message classes, such as
HelloRequest
andHelloReply
GreeterService
interface of the serviceGreeterServiceClient
that implements the client side of theGreeterService
On the client side we don’t have to implement anything, the GreeterServiceClient
is ready to be used as is.
We need an ActorSystem
and then the GreeterServiceClient
can be created and used like this:
sourceimport akka.Done;
import akka.NotUsed;
import akka.japi.Pair;
import akka.actor.typed.ActorSystem;
import akka.actor.typed.javadsl.Behaviors;
import akka.grpc.GrpcClientSettings;
import akka.stream.javadsl.Source;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletionStage;
import static akka.NotUsed.notUsed;
class GreeterClient {
public static void main(String[] args) {
final ActorSystem<Void> system = ActorSystem.create(Behaviors.empty(), "GreeterClient");
GreeterServiceClient client = GreeterServiceClient.create(
GrpcClientSettings.fromConfig("helloworld.GreeterService", system),
system
);
final List<String> names;
if (args.length == 0) {
names = Arrays.asList("Alice", "Bob");
} else {
names = Arrays.asList(args);
}
names.forEach(name -> {
System.out.println("Performing request: " + name);
HelloRequest request = HelloRequest.newBuilder()
.setName(name)
.build();
CompletionStage<HelloReply> replyCS = client.sayHello(request);
replyCS.whenComplete((reply, error) -> {
if (error == null) {
System.out.println(reply.getMessage());
} else {
System.out.println(error.getMessage());
}
});
});
}
}
Note that clients and servers don’t have to be implemented with Akka gRPC. They can be implemented/used with other libraries or languages and interoperate according to the gRPC specification.
Other types of calls
In this first example we saw a gRPC service call for single request returning a CompletionStage
reply. The parameter and return type of the calls may also be streams in 3 different combinations:
- client streaming call -
Source
(stream) of requests from the client that returns aCompletionStage
with a single response, seeitKeepsTalking
in above example - server streaming call - single request that returns a
Source
(stream) of responses, seeitKeepsReplying
in above example - client and server streaming call -
Source
(stream) of requests from the client that returns aSource
(stream) of responses, seestreamHellos
in above example
As next step, let’s try the bidirectional streaming calls.