Migration Guide 2.4.x to 2.5.x

Actor (Java)

AbstractActor

AbstractActor has been promoted from its experimental/may change state and while doing this we did some small, but important, improvements to the API that will require some mechanical changes of your source code.

Previously the receive behavior was set with the receive method, but now an actor has to define its initial receive behavior by implementing the createReceive method in the AbstractActor. This has the advantages:

  • It gives a clear entry point of what to implement. The compiler tells you that the abstract method must be implemented.
  • It’s impossible to forget to set the receive behavior.
  • It’s not possible to define the receive behavior more than once.

The return type of createReceive is AbstractActor.Receive. It defines which messages your Actor can handle, along with the implementation of how the messages should be processed. You can build such behavior with a builder named ReceiveBuilder.

AbstractActor.Receive can also be used in getContext().become.

The old receive method exposed Scala’s PartialFunction and BoxedUnit in the signature, which are unnecessary concepts for newcomers to learn. The new createReceive requires no additional imports.

Note that The Receive can still be implemented in other ways than using the ReceiveBuilder since it in the end is a wrapper around a Scala PartialFunction. For example, one could implement an adapter to Javaslang Pattern Matching DSL.

The mechanical source code change for migration to the new AbstractActor is to implement the createReceive instead of calling receive (compiler will tell that this is missing).

Old:

import akka.actor.AbstractActor;
import akka.japi.pf.ReceiveBuilder;
import scala.PartialFunction;
import scala.runtime.BoxedUnit;

public class SomeActor extends AbstractActor {
  public SomeActor() {
    receive(ReceiveBuilder
      .match(String.class, s -> System.out.println(s.toLowerCase())).
      .build());
  }
}

New:

import akka.actor.AbstractActor;

public class SomeActor extends AbstractActor {
  @Override
  public Receive createReceive() {
    return receiveBuilder()
      .match(String.class, s -> System.out.println(s.toLowerCase()))
      .build();
  }
}

See Receive messages documentation for more advice about how to implement createReceive.

A few new methods have been added with deprecation of the old. Worth noting is preRestart.

Old:

@Override
public void preRestart(Throwable reason, scala.Option<Object> message) {
  super.preRestart(reason, message);
}

New:

@Override
public void preRestart(Throwable reason, java.util.Optional<Object> message) {
  super.preRestart(reason, message);
}

AbstractPersistentActor

Similar change as described above for AbstractActor is needed for AbstractPersistentActor. Implement createReceiveRecover instead of receiveRecover, and createReceive instead of receiveCommand.

Old:

@Override
public PartialFunction<Object, BoxedUnit> receiveCommand() {
  return ReceiveBuilder.
    match(String.class, cmd -> {/* ... */}).build();
}

@Override
public PartialFunction<Object, BoxedUnit> receiveRecover() {
  return ReceiveBuilder.
      match(String.class, evt -> {/* ... */}).build();
}

New:

@Override
public Receive createReceive() {
  return receiveBuilder().
    match(String.class, cmd -> {/* ... */}).build();
}

@Override
public Receive createReceiveRecover() {
  return receiveBuilder().
      match(String.class, evt -> {/* ... */}).build();
}

UntypedActor

UntypedActor has been deprecated in favor of AbstractActor. As a migration path you can extend UntypedAbstractActor instead of UntypedActor.

Old:

import akka.actor.UntypedActor;

public class SomeActor extends UntypedActor {

  public static class Msg1 {}

  @Override
  public void onReceive(Object msg) throws Exception {
    if (msg instanceof Msg1) {
      Msg1 msg1 = (Msg1) msg;
      // actual work
    } else {
      unhandled(msg);
    }
  }
}

New:

import akka.actor.UntypedAbstractActor;

public class SomeActor extends UntypedAbstractActor {

  public static class Msg1 {}

  @Override
  public void onReceive(Object msg) throws Exception {
    if (msg instanceof Msg1) {
      Msg1 msg1 = (Msg1) msg;
      // actual work
    } else {
      unhandled(msg);
    }
  }
}

It’s recommended to migrate UntypedActor to AbstractActor by implementing createReceive instead of onReceive.

Old:

import akka.actor.UntypedActor;

public class SomeActor extends UntypedActor {

  @Override
  public void onReceive(Object msg) throws Exception {
    if (msg instanceof String) {
      String s = (String) msg;
      System.out.println(s.toLowerCase());
    } else {
      unhandled(msg);
    }
  }
}

New:

import akka.actor.AbstractActor;

public class SomeActor extends AbstractActor {
  @Override
  public Receive createReceive() {
    return receiveBuilder()
      .match(String.class, s -> {
        System.out.println(s.toLowerCase());
      })
      .build();
  }
}

See Receive messages documentation for more advice about how to implement createReceive.

Similar with UntypedActorWithStash, UntypedPersistentActor, and UntypedPersistentActorWithAtLeastOnceDelivery.

Actor (Scala)

Actor DSL deprecation

Actor DSL is a rarely used feature and thus will be deprecated and removed. Use plain system.actorOf instead of the DSL to create Actors if you have been using it.

ExtensionKey Deprecation

ExtensionKey is a shortcut for writing Akka Extensions but extensions created with it cannot be used from Java and it does in fact not save many lines of code over directly implementing ExtensionId.

Old:

object MyExtension extends ExtensionKey[MyExtension]

New:

object MyExtension extends ExtensionId[MyExtension] with ExtensionIdProvider {

  override def lookup = MyExtension

  override def createExtension(system: ExtendedActorSystem): MyExtension =
    new MyExtension(system)

  // needed to get the type right when used from Java
  override def get(system: ActorSystem): MyExtension = super.get(system)
}

Actor Mailbox

Scala 2.12 is using the standard JDK8 ForkJoinPool, which may cause performance regression for some Actor messaging scenarios. Therefore we have embedded the ForkJoinPool from Scala 2.11 in Akka. This breaks binary backwards compatibility for custom Mailbox implementations that were compiled with Akka 2.4.

This is only a problem if you are using a library that provides a custom Mailbox implementation. The change is source compatible and such library should be recompiled and released for Akka 2.5.

Streams

Removal of StatefulStage, PushPullStage

StatefulStage and PushPullStage were first introduced in Akka Streams 1.0, and later deprecated and replaced by GraphStage in 2.0-M2. The GraphStage API has all features (and even more) as the previous APIs and is even nicer to use.

Please refer to the GraphStage documentation, for details on building custom GraphStages.

StatefulStage would be migrated to a simple GraphStage that contains some mutable state in its GraphStageLogic, and PushPullStage directly translate to graph stages.

Removal of Source.transform, replaced by via

Along with the removal of Stage (as described above), the transform methods creating Flows/Sources/Sinks from Stage have been removed. They are replaced by using GraphStage instances with via, e.g.:

exampleFlow.transform(() => new MyStage())

would now be:

myFlow.via(new MyGraphStage)

as the GraphStage itself is a factory of logic instances.

SubFlow.zip and SubSource.zip now emit akka.japi.Pair instead of Scala’s Pair

The the Java API’s zip operator on SubFlow and SubSource has been emitting Scala’s Pair (Tuple2) instead of akka.japi.Pair. This is fixed in Akka 2.5 where it emits the proper Java DSl type.

Please note that the zip operator on Source and Flow has had the correct type, this change only affects the Sub... versions of those classes.

Deprecation of ActorSubscriber and ActorPublisher

The classes ActorPublisher and ActorSubscriber were the first user-facing Reactive Streams integration API that we provided for end-users. Akka Streams APIs have evolved and improved a lot since then, and now there is no need to use these low-level abstractions anymore. It is easy to get things wrong when implementing them, and one would have to validate each implementation of such Actor using the Reactive Streams Technology Compatibility Kit.

The replacement API is the powerful GraphStage. It has all features that raw Actors provided for implementing Stream stages and adds additional protocol and type-safety. You can learn all about it in the documentation: Custom stream processing.

You should also read the blog post series on the official team blog, starting with Mastering GraphStages, part I, which explains using and implementing GraphStages in more practical terms than the reference documentation.

Order of Attributes List

Imporant performance improvement could be achieved by reverting the order of the attributesList in Attributes.

The attributeList is ordered with the most specific attribute first, least specific last. Note that the order was the opposite in Akka 2.4.x (but it was not really documented).

The semantics of the convenience methods, such as get and getFirst are the same, but if you use the attributesList directly or via filtered or getAttributeList you need to take the new order into consideration.

Dispatcher attribute

The ActorAttributes.dispatcher attribute is adding an async boundary in 2.5, since that is the typical desired behavior. In 2.4 an explicit async marker (AsyncBoundary attribute) had to be added. For example, this means that Source that defined blocking-io-dispatcher as default followed by a map will now be separated by an async boundary, which was not the case in 2.4.

Removal of the auto-fuse setting

In 2.4 fusing stages together into the same actor could be completely disabled with the setting akka.stream.materializer.auto-fusing. The new materializer introduced in Akka 2.5 does not support disabling fusing, so this setting does not have any effect any more and has been deprecated. Running each stage in a stream on a separate actor can be done by adding explicit async boundaries around every stage. How to add asynchronous boundaries can be seen in Operator Fusion.

Remote

Mutual TLS authentication now required by default for netty-based SSL transport

Mutual TLS authentication is now required by default for the netty-based SSL transport.

Nodes that are configured with this setting to on might not be able to receive messages from nodes that run on older versions of akka-remote. This is because in versions of Akka < 2.4.12 the active side of the remoting connection will not send over certificates even if asked to.

It is still possible to make a rolling upgrade from a version < 2.4.12 by doing the upgrade stepwise: : * first, upgrade Akka to the latest version but keep akka.remote.netty.ssl.require-mutual-authentication at off and do a first rolling upgrade * second, turn the setting to on and do another rolling upgrade

For more information see the documentation for the akka.remote.netty.ssl.require-mutual-authentication configuration setting in akka-remote’s reference.conf.

additional-serialization-bindings

From Akka 2.5.0 the additional-serialization-bindings are enabled by default. That defines serializers that are replacing some Java serialization that were used in 2.4. This setting was disabled by default in Akka 2.4.16 but can also be enabled in an Akka 2.4 system.

To still be able to support rolling upgrade from a system with this setting disabled, e.g. default for 2.4.16, it is possible to disable the additional serializers and continue using Java serialization for those messages.

akka.actor {
  # Set this to off to disable serialization-bindings defined in
  # additional-serialization-bindings. That should only be needed
  # for backwards compatibility reasons.
  enable-additional-serialization-bindings = off
}

Please note that this setting must be the same on all nodes participating in a cluster, otherwise the mis-aligned serialization configurations will cause deserialization errors on the receiving nodes.

After performing a rolling upgrade from 2.4 to 2.5 with enable-additional-serialization-bindings = off you can perform another rolling upgrade and change this setting to on. See also Rolling upgrades.

With serialize-messages the deserialized message is actually sent

The flag akka.actor.serialize-messages = on triggers serialization and deserialization of each message sent in the ActorSystem. With this setting enabled the message actually passed on to the actor previously was the original message instance, this has now changed to be the deserialized message instance.

This may cause tests that rely on messages being the same instance (for example by having mutable messages with attributes that are asserted in the tests) to not work any more with this setting enabled. For such cases the recommendation is to either not rely on messages being the same instance or turn the setting off.

Wire Protocol Compatibility

It is possible to use Akka Remoting between nodes running Akka 2.4.16 and 2.5.x, but some settings have changed so you might need to adjust some configuration as described in Rolling Update.

Note however that if using Java serialization it will not be possible to mix nodes using Scala 2.11 and 2.12.

Cluster

Rolling Update

It is possible to do a rolling update from Akka 2.4.16 to 2.5.x, i.e. running a cluster of 2.4.16 nodes and join nodes running 2.5.x followed by shutting down the old nodes.

You must first update all nodes to 2.4.16. It’s not supported to update directly from an older version than 2.4.16 to 2.5.x. For example, if you are running 2.4.11 you must first do a rolling update to 2.4.16, shut down all 2.4.11 nodes, and then do the rolling update to 2.5.x.

For some configuration settings it’s important to use the same values on all nodes in the cluster. Some settings have changed default value in 2.5.0 and therefore you need to review your configuration before doing a rolling update to 2.5.x. Such settings are mentioned elsewhere in this migration guide and here is a summary of things to consider.

See also the rolling update guide for specifics about later patch releases.

Limit lookup of routees to nodes tagged with multiple roles

Starting with 2.5.4, cluster routing supports delivering messages to routees tagged with all specified roles using use-roles (instead of use-role in previous versions). When doing rolling upgrades and using this new feature, it is important to first upgrade the existing nodes to the latest version of Akka and then start using multiple roles in a separate rolling upgrade. Otherwise, if a new node sends a message with the restriction use-roles = ["a", "b"], that will only require the “a” role on old nodes.

Coordinated Shutdown

There is a new extension named CoordinatedShutdown that will stop certain actors and services in a specific order and perform registered tasks during the shutdown process.

When using Akka Cluster, tasks for graceful leaving of cluster including graceful shutdown of Cluster Singletons and Cluster Sharding are now performed automatically.

Previously it was documented that things like terminating the ActorSystem should be done when the cluster member was removed, but this was very difficult to get right. That is now taken care of automatically. This might result in changed behavior, hopefully to the better. It might also be in conflict with your previous shutdown code so please read the documentation for the Coordinated Shutdown and revisit your own implementations. Most likely your implementation will not be needed any more or it can be simplified.

More information can be found in the documentation.

For some tests it might be undesired to terminate the ActorSystem via CoordinatedShutdown. You can disable that by adding the following to the configuration of the ActorSystem that is used in the test:

# Don't terminate ActorSystem via CoordinatedShutdown in tests
akka.coordinated-shutdown.terminate-actor-system = off
akka.coordinated-shutdown.run-by-jvm-shutdown-hook = off
akka.cluster.run-coordinated-shutdown-when-down = off

WeaklyUp

WeaklyUp is now enabled by default, but it can be disabled with configuration option:

akka.cluster.allow-weakly-up-members = off

You should not run a cluster with this feature enabled on some nodes and disabled on some. Therefore you might need to enable/disable it in configuration when performing rolling upgrade from 2.4.x to 2.5.0.

Cluster Sharding state-store-mode

Distributed Data mode, which was still experimental in 2.4.x, is now the default state-store-mode for Cluster Sharding. The persistence mode is also supported. Read more in the documentation.

It’s important to use the same mode on all nodes in the cluster, i.e. if you perform a rolling upgrade from 2.4.16 you might need to change the state-store-mode to be the same (persistence is default in 2.4.x):

akka.cluster.sharding.state-store-mode = persistence

Note that the stored Remembering Entities data with persistence mode cannot be migrated to the data mode. Such entities must be started again in some other way when using ddata mode.

Rolling upgrades from clusters that already used the (then-experimental) ddata mode are not supported.

Cluster Sharding remember entities

To use remember entities with cluster sharding there are now an additional requirement added: the extractShardId must be able to extract the shard id from the message Shard.StartEntity(EntityId). This is implies that it must be possible to calculate a shard id from an entity id when using remember entities.

This was added to be able to gracefully handle when persisted locations of entities does not match where the entities should live when a shard region starts up. Such states could be cause by changing the extractShardId logic and restart a system using remember entities.

Cluster Management Command Line Tool

There is a new cluster management tool with HTTP API that has the same functionality as the command line tool. The HTTP API gives you access to cluster membership information as JSON including full reachability status between the nodes. It supports the ordinary cluster operations such as join, leave, and down.

See documentation of Akka Management.

The command line script for cluster management has been deprecated and is scheduled for removal in the next major version. Use the HTTP API with curl or similar instead.

Distributed Data

Distributed Data has been promoted to a stable module. This means that we will keep the API stable from this point. As a result the module name is changed from akka-distributed-data-experimental to akka-distributed-data and you need to change that in your build tool (sbt/mvn/…).

Map allow generic type for the keys

In 2.4 the key of any Distributed Data map always needed to be of type String. In 2.5 you can use any type for the key. This means that every map (ORMap, LWWMap, PNCounterMap, ORMultiMap) now takes an extra type parameter to specify the key type. To migrate existing code from 2.4 to 2.5 you simple add String as key type, for example: ORMultiMap[Foo] becomes ORMultiMap[String, Foo]. PNCounterMap didn’t take a type parameter in version 2.4, so PNCounterMap in 2.4 becomes PNCounterMap[String] in 2.5. Java developers should use <> instead of [], e.g: PNCounterMap.

NOTE: Even though the interface is not compatible between 2.4 and 2.5, the binary protocol over the wire is (as long as you use String as key type). This means that 2.4 nodes can synchronize with 2.5 nodes.

Subscribers

When an entity is removed subscribers will not receive Replicator.DataDeleted any more. They will receive Replicator.Deleted instead.

Persistence

Binary incompatibility of PersistentActor and AtLeastOneDelivery

To be able to evolve the Java APIs AbstractPersistentActor and AbstractPersistentActorWithAtLeastOnceDelivery to work with Scala 2.12 we could find no other solution but to break the binary compatibility of the Scala versions (which the Java ones were based on).

This means that the Akka 2.5 artifact cannot be a class path drop in replacement of Akka 2.4 if you use PersistentActor or AtLeastOnceDelivery, to do this upgrade you must recompile your project with the new version of Akka.

Removal of PersistentView

After being deprecated for a long time, and replaced by Persistence Query PersistentView has been removed now removed.

The corresponding query type is EventsByPersistenceId. There are several alternatives for connecting the Source to an actor corresponding to a previous PersistentView. There are several alternatives for connecting the Source to an actor corresponding to a previous PersistentView actor which are documented in Integration.

The consuming actor may be a plain Actor or an PersistentActor if it needs to store its own state (e.g. fromSequenceNr offset).

Please note that Persistence Query is not experimental/may-change anymore in Akka 2.5.0, so you can safely upgrade to it.

Persistence Plugin Proxy

A new persistence plugin proxy was added, that allows sharing of an otherwise non-sharable journal or snapshot store. The proxy is available by setting akka.persistence.journal.plugin or akka.persistence.snapshot-store.plugin to akka.persistence.journal.proxy or akka.persistence.snapshot-store.proxy, respectively. The proxy supplants the Shared LevelDB journal.

Persistence Query

Persistence Query has been promoted to a stable module. As a result the module name is changed from akka-persistence-query-experimental to akka-persistence-query and you need to change that in your build tool (sbt/mvn/…). Only slight API changes were made since the module was introduced:

Query naming consistency improved

Queries always fall into one of the two categories: infinite or finite (“current”). The naming convention for these categories of queries was solidified and is now as follows:

  • “infinite” - e.g. eventsByTag, persistenceIds - which will keep emitting events as they are persisted and match the query.
  • “finite”, also known as “current” - e.g. currentEventsByTag, currentPersistenceIds - which will complete the stream once the query completed, for the journal’s definition of “current”. For example in an SQL store it would mean it only queries the database once.

Only the AllPersistenceIdsQuery class and method name changed due to this. The class is now called PersistenceIdsQuery, and the method which used to be allPersistenceIds is now persistenceIds.

Queries now use Offset instead of Long for offsets

This change was made to better accommodate the various types of Journals and their understanding what an offset is. For example, in some journals an offset is always a time, while in others it is a numeric offset (like a sequence id).

Instead of the previous Long offset you can now use the provided Offset factories (and types):

  • akka.persistence.query.Offset.sequence(value: Long),
  • akka.persistence.query.Offset.timeBasedUUID(value: UUID)
  • and finally NoOffset if not offset should be used.

Journals are also free to provide their own specific Offset types. Consult your journal plugin’s documentation for details.

Agents

Agents are now deprecated

Akka Agents are a very simple way of containing mutable state and allowing to access it safely from multiple threads. The abstraction is leaky though, as Agents do not work over the network (unlike Akka Actors).

As users were often confused by “when to use an Actor vs. when to use an Agent?” a decision was made to deprecate the Agents, as they rarely are really enough and do not fit the Akka spirit of thinking about distribution. We also anticipate to replace the uses of Agents by the upcoming Akka Typed, so in preparation thereof the Agents have been deprecated in 2.5.

If you use Agents and would like to take over the maintenance thereof, please contact the team on Gitter or GitHub.

Camel

akka-camel has been deprecated in favour of Alpakka , the Akka Streams based collection of integrations to various endpoints (including Camel)

We acknowledge that Akka Camel is a very useful and important module. It will not be removed until Alpakka has reached the needed production quality to be a full replacement. The deprecation of Akka Camel should be seen as a signal that new development is to be invested in Alpakka instead of Akka Camel.

Contrib

akka-contrib has been deprecated and is scheduled for removal in the next major version. The reason is to reduce the amount of things to maintain in the core Akka projects. Contributions to the core of Akka or its satellite projects are welcome. Contributions that don’t fit into existing modules can be hosted in new Akka GitHub repositories in the akka GitHub organization or outside of it depending on what kind of library it is. Please ask.

Aggregator

Aggregator has been deprecated. Feel free to copy the source into your project or create a separate library outside of Akka.

CircuitBreakerProxy

CircuitBreakerProxy has been deprecated in favor of akka.pattern.CircuitBreaker with explicit ask requests.

JavaLogger

akka.contrib.jul.JavaLogger has been deprecated and included in akka-actor instead as akka.event.jul.JavaLogger. See documentation.

The JavaLoggingAdapter has also been deprecated, but not included in akka-actor. Feel free to copy the source into your project or create a separate library outside of Akka.

PeekMailbox

PeekMailbox has been deprecated. Use an explicit supervisor or proxy actor instead.

ReceivePipeline

ReceivePipeline has been deprecated. Feel free to copy the source into your project or create a separate library outside of Akka.

ReliableProxy

ReliableProxy has been deprecated. Use At-Least-Once Delivery instead. ReliableProxy was only intended as an example and doesn’t have full production quality. If there is demand for a lightweight (non-durable) at-least once delivery mechanism we are open for a design discussion.

TimerBasedThrottler

TimerBasedThrottler has been deprecated. Use the throttle stage in Akka Streams instead.

Example in Scala:

import scala.concurrent.duration._
import akka.NotUsed
import akka.actor.ActorRef
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.OverflowStrategy
import akka.stream.ThrottleMode
import akka.stream.scaladsl.Sink
import akka.stream.scaladsl.Source

val system: ActorSystem = ??? // TODO real ActorSystem here
val target: ActorRef = ??? // TODO real target ActorRef here
implicit val materializer = ActorMaterializer.create(system)

val throttler: ActorRef =
  Source.actorRef(bufferSize = 1000, OverflowStrategy.dropNew)
    .throttle(100, 1.second)
    .to(Sink.actorRef(target, NotUsed))
    .run()

Example in Java:

import java.util.concurrent.TimeUnit;
import java.time.Duration;
import akka.NotUsed;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.OverflowStrategy;
import akka.stream.ThrottleMode;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;

final ActorSystem system = null; // TODO real ActorSystem here
final ActorRef target = null; // TODO real target ActorRef here
final Materializer materializer = ActorMaterializer.create(system);

final ActorRef throttler =
  Source.actorRef(1000, OverflowStrategy.dropNew())
    .throttle(100,  Duration.ofSeconds(1))
    .to(Sink.actorRef(target, NotUsed.getInstance()))
    .run(materializer);

Note, that when using Sink.actorRef the sender of the original message sent to the thottler ActorRef will be lost and messages will arrive at the target with the sender set to ActorRef.noSender. Using this construct it is currently impossible to keep the original sender. Alternatively, you can manually specify a static sender by replacing Sink.actorRef with Sink.foreach(msg -> target.tell(msg, whateverSender))Sink.foreach(msg => target.tell(msg, whateverSender)). You could also calculate a sender by inspecting the msg. Be cautious not to use target.tell(msg, sender()) inside of Sink.foreach because the result of sender() is undefined or will fail when executed from within a stream.

Akka Typed

With the new term may change we will no longer have a different artifact for modules that are not stable, and akka-typed-experimental has therefore been renamed to akka-typed. Note that it is still not promoted to a stable module.

Experimental modules

We have previously marked modules that we did not want to freeze the APIs of a experimental, such modules will instead be marked as may change from now on.

Testkit

JavaTestKit has been deprecated since it does all kinds of tricks to achieve what Scala testkit does. Use akka.testkit.javadsl.TestKit instead which introduces nicer APIs.

Old:

new JavaTestKit(system) {{
  final JavaTestKit probe = new JavaTestKit(system);
  new Within(duration("1 second")) {
    public void run() {
      probe.expectMsgEquals("hello");
    }
  };
}};

New:

new TestKit(system) {{
  final TestKit probe = new TestKit(system);
  within(duration("1 second"), () -> probe.expectMsgEquals("hello"));
}};
Found an error in this documentation? The source code for this page can be found here. Please feel free to edit and contribute a pull request.