Skip to main content
Version: 2.0.x

Client wrappers

Service modules

For each AWS Service the library defines a ZIO service with wrapper functions for all the operations, a live implementation calling the Java SDK and a mock implementation using zio-mock.

The live implementation depends on a core AWS configuration layer:

val live: ZLayer[AwsConfig, Throwable, Ec2]

The AwsConfig layer defines how each service's async Java client gets configured, including the http client which is provided by another layer AwsConfig is depending on.

Each module has accessor functions for all operations of the given service.


For simple request-response operations the library generates a very light wrapper:

def deleteVolume(request: DeleteVolumeRequest): ZIO[Ec2, AwsError, DeleteVolumeResponse.ReadOnly]

For operations where either the input or the output or both are byte streams, a ZStream wrapper is generated:

def getObject(request: GetObjectRequest): ZIO[S3, AwsError, StreamingOutputResult[Any, GetObjectResponse.ReadOnly, Byte]]
def putObject(request: PutObjectRequest, body: ZStream[Any, AwsError, Byte]): ZIO[S3, AwsError, PutObjectResponse.ReadOnly]

where the output is a stream packed together with additional response data:

case class StreamingOutputResult[R, Response, Item](
response: Response,
output: ZStream[R, AwsError, Item]

For operations with event streams a ZStream of a model type gets generated:

def startStreamTranscription(request: StartStreamTranscriptionRequest, input: ZStream[Any, AwsError, AudioStream]): ZStream[TranscribeStreaming, AwsError, TranscriptEvent.ReadOnly]

And for all operations that supports pagination, streaming wrappers gets generated:

def scan(request: ScanRequest): ZStream[DynamoDb, AwsError, Map[AttributeName, AttributeValue.ReadOnly]]

Note that for event streaming or paginating operations returning a ZStream the actual AWS call happens when the stream gets pulled.

For use cases when calling the paginating interface directly is necessary - for example when forwarding paginated results through a HTTP API, the library generates non-streaming wrappers as well for these methods.

For example the DynamoDB scan method's non-streaming variant is defined as:

def scanPaginated(request: ScanRequest): ZIO[DynamoDb, ScanResponse.ReadOnly]

Model wrappers

For each model type a set of wrappers are generated, providing the following functionality:

  • Case classes with default parameter values instead of the builder pattern
  • zio-prelude's newtype wrappers for primitive types
  • Automatic conversion to Scala collection types
  • ADTs instead of the Java enums
  • ZIO getter functions to "get or fail" the optional model fields
  • Using zio-prelude's Optional type to eliminate boilerplate when constructing models with many optional fields

The following example from the zio-aws-elasticsearch library shows how the generated case classes look like, to be used as input for the service operations:

case class DescribePackagesFilter(name: Optional[DescribePackagesFilterName] = Optional.Absent, 
value: Optional[Iterable[primitives.DescribePackagesFilterValue]] = Optional.Absent) {
def buildAwsValue(): = {
import DescribePackagesFilter.zioAwsBuilderHelper.BuilderOps
.optionallyWith( => value.unwrap))(
.optionallyWith( => { item => item: java.lang.String }.asJava))(_.value)

def asReadOnly: DescribePackagesFilter.ReadOnly = DescribePackagesFilter.wrap(buildAwsValue())

When processing the results of the operations (either directly or though the ZStream wrappers), the AWS Java model types are wrapped by a read-only wrapper interface. The following example shows one from the transcribe module:

object CreateMedicalVocabularyResponse {
private lazy val zioAwsBuilderHelper: BuilderHelper[] = BuilderHelper.apply

trait ReadOnly {
def editable: CreateMedicalVocabularyResponse = CreateMedicalVocabularyResponse( => value), => value), => value), => value), => value))
def vocabularyName: Optional[VocabularyName]
def languageCode: Optional[LanguageCode]
def vocabularyState: Optional[VocabularyState]
def lastModifiedTime: Optional[DateTime]
def failureReason: Optional[FailureReason]
def getVocabularyName: ZIO[Any, AwsError, VocabularyName] = AwsError.unwrapOptionField("vocabularyName", vocabularyNameValue)
def getLanguageCode: ZIO[Any, AwsError, LanguageCode] = AwsError.unwrapOptionField("languageCode", languageCodeValue)
def getVocabularyState: ZIO[Any, AwsError, VocabularyState] = AwsError.unwrapOptionField("vocabularyState", vocabularyStateValue)
def getLastModifiedTime: ZIO[Any, AwsError, DateTime] = AwsError.unwrapOptionField("lastModifiedTime", lastModifiedTimeValue)
def getFailureReason: ZIO[Any, AwsError, FailureReason] = AwsError.unwrapOptionField("failureReason", failureReasonValue)

private class Wrapper(impl: extends CreateMedicalVocabularyResponse.ReadOnly {
// ... implements the ReadOnly interface by querying the underlying Java object

def wrap(impl: ReadOnly = new Wrapper(impl)

As a large part of the models in the AWS SDK are defined as optional, the generated wrapper also contains ZIO accessor functions, which lift the option value to make it more comfortable to chain the AWS operations.


Each module also contains generated ZIO Test mocks for the given service.

The following example shows how to use them with the zio-aws-athena library:

val athena = AthenaMock.StartQueryExecution(
(startQueryExecutionRequest: StartQueryExecutionRequest) =>