sttp

Unified HTTP client library for Scala. Provides common interface supporting multiple backends (Java HttpClient, Akka HTTP, http4s, OkHttp, etc.). Seamless integration with JSON processing (circe, uPickle, etc.), streaming (fs2, ZIO Streams, etc.), and functional programming libraries.

HTTP ClientScalaFunctionalType-safeMulti-platformAsynchronous

GitHub Overview

softwaremill/sttp

The Scala HTTP client you always wanted!

Stars1,481
Watchers37
Forks322
Created:June 30, 2017
Language:Scala
License:Apache License 2.0

Topics

akka-httpasynchronouscatsclientdistributed-tracinghttphttp-clienthttpclientinterpolatormonixokhttpreactive-streamsscalascalazsynchronousurizipkinzipkin-brave

Star History

softwaremill/sttp Star History
Data as of: 7/18/2025, 01:39 AM

Library

sttp

Overview

sttp is "The Scala HTTP client you always wanted!" developed as a next-generation HTTP client library in the Scala ecosystem, designed with emphasis on "unified API design and diverse programming paradigm support." It enables unified API usage across synchronous processing, Future-based, and functional effect systems (cats-effect, ZIO, Monix, etc.). Through immutable request definitions, pluggable backends, type-safe response handling, URI interpolation, and rich ecosystem integration, it enables intuitive and efficient HTTP communication across various Scala programming styles.

Details

sttp 2025 edition (v4.0 series) is a mature Scala HTTP client solution co-developed by SoftwareMill and VirtusLab. The core abstractions of Request[T], Backend[F], and Response[T] clearly separate request description, execution, and response handling. Features include multi-platform design supporting Scala 2.12/2.13/3, JVM (Java 11+)/Scala.JS/Scala Native compatibility, pluggable backends (Java HttpClient, Akka HTTP, Pekko HTTP, http4s, OkHttp, etc.), comprehensive authentication support, streaming integration, and testing support tools. With an API emphasizing developer experience and productivity improvement, it addresses diverse needs of Scala developers.

Key Features

  • Unified API Design: Consistent development experience across synchronous, asynchronous, and functional effect systems
  • Immutable Request Definition: Type-safe and reusable HTTP request description via Request[T]
  • Pluggable Backends: Flexible HTTP implementation switching through Backend[F] abstraction
  • Type-safe Response Handling: Powerful type-safe response transformation via ResponseAs[T]
  • Multi-platform Support: Unified support for JVM/Scala.JS/Scala Native
  • Rich Ecosystem Integration: Tight integration with JSON, streaming, and monitoring libraries

Pros and Cons

Pros

  • Highest level of integration and developer experience in the Scala ecosystem
  • Natural affinity with functional programming paradigms
  • Significant reduction in runtime errors through strong type safety
  • High flexibility and customizability through pluggable architecture
  • Comprehensive documentation and active community support
  • Wide applicability through multi-platform support

Cons

  • High learning cost due to Scala-specific concept understanding requirements
  • Initial setup complexity due to rich functionality
  • Required understanding of functional effect systems
  • Limitations in interoperability with Java/other language ecosystems
  • May be overly feature-rich for small projects
  • Potential increase in compilation time

Reference Pages

Code Examples

Installation and Basic Setup

// build.sbt - Basic configuration
libraryDependencies ++= Seq(
  "com.softwaremill.sttp.client4" %% "core" % "4.0.9"
)

// JSON processing integration
libraryDependencies ++= Seq(
  "com.softwaremill.sttp.client4" %% "core" % "4.0.9",
  "com.softwaremill.sttp.client4" %% "circe" % "4.0.9",  // circe integration
  "io.circe" %% "circe-generic" % "0.14.7"
)

// ZIO integration
libraryDependencies ++= Seq(
  "com.softwaremill.sttp.client4" %% "core" % "4.0.9",
  "com.softwaremill.sttp.client4" %% "zio" % "4.0.9",
  "dev.zio" %% "zio" % "2.1.6"
)

// cats-effect integration
libraryDependencies ++= Seq(
  "com.softwaremill.sttp.client4" %% "core" % "4.0.9",
  "com.softwaremill.sttp.client4" %% "cats" % "4.0.9",
  "org.typelevel" %% "cats-effect" % "3.5.4"
)
package main

import sttp.client4.*

// Basic client initialization
object SttpBasics {
  // Quick start for experimentation and learning
  def quickExample(): Unit = {
    import sttp.client4.quick.*
    
    // Immediate request with global synchronous backend
    val response = quickRequest.get(uri"https://api.example.com/users").send()
    println(response.body)
  }
  
  // Production synchronous backend
  def syncExample(): Unit = {
    val backend = DefaultSyncBackend()
    
    val request = basicRequest
      .get(uri"https://api.example.com/users")
      .header("Accept", "application/json")
      .header("User-Agent", "MyScalaApp/1.0")
    
    val response = request.send(backend)
    
    response.body match {
      case Right(content) => println(s"Success: $content")
      case Left(error) => println(s"Error: $error")
    }
    
    backend.close()
  }
  
  // Future-based asynchronous backend
  def futureExample(): Unit = {
    import scala.concurrent.{Future, ExecutionContext}
    import scala.util.{Success, Failure}
    
    implicit val ec: ExecutionContext = ExecutionContext.global
    val backend = DefaultFutureBackend()
    
    val request = basicRequest.get(uri"https://api.example.com/users")
    val responseFuture: Future[Response[Either[String, String]]] = request.send(backend)
    
    responseFuture.onComplete {
      case Success(response) => 
        println(s"Response: ${response.body}")
      case Failure(exception) => 
        println(s"Failed: ${exception.getMessage}")
    }
    
    backend.close()
  }
}

Basic HTTP Requests (GET/POST/PUT/DELETE)

import sttp.client4.*
import io.circe.*
import io.circe.generic.semiauto.*
import sttp.client4.circe.*

// Data class definitions
case class User(id: Int, name: String, email: String, age: Int)
case class UserCreateRequest(name: String, email: String, age: Int)
case class UserUpdateRequest(name: Option[String], email: Option[String], age: Option[Int])

// JSON Codec definitions
implicit val userDecoder: Decoder[User] = deriveDecoder[User]
implicit val userEncoder: Encoder[User] = deriveEncoder[User]
implicit val userCreateEncoder: Encoder[UserCreateRequest] = deriveEncoder[UserCreateRequest]
implicit val userUpdateEncoder: Encoder[UserUpdateRequest] = deriveEncoder[UserUpdateRequest]

object HttpRequestExamples {
  val backend = DefaultSyncBackend()
  
  // GET request (basic)
  def getUserList(): Either[String, List[User]] = {
    val response = basicRequest
      .get(uri"https://api.example.com/users")
      .response(asJson[List[User]])
      .send(backend)
    
    response.body match {
      case Right(users) => Right(users)
      case Left(error) => Left(s"Failed to get user list: $error")
    }
  }
  
  // GET request with query parameters
  def getUserListWithParams(page: Int, limit: Int, sortBy: String): Either[String, List[User]] = {
    val response = basicRequest
      .get(uri"https://api.example.com/users?page=$page&limit=$limit&sort=$sortBy")
      .response(asJson[List[User]])
      .send(backend)
    
    response.body match {
      case Right(users) => 
        println(s"Retrieved user count: ${users.length}")
        Right(users)
      case Left(error) => Left(s"Failed to get page: $error")
    }
  }
  
  // GET request (individual user)
  def getUser(userId: Int): Either[String, User] = {
    val response = basicRequest
      .get(uri"https://api.example.com/users/$userId")
      .response(asJson[User])
      .send(backend)
    
    response.body match {
      case Right(user) => Right(user)
      case Left(error) => Left(s"Failed to get user: $error")
    }
  }
  
  // POST request (create user)
  def createUser(userRequest: UserCreateRequest): Either[String, User] = {
    val response = basicRequest
      .post(uri"https://api.example.com/users")
      .body(userRequest)
      .response(asJson[User])
      .send(backend)
    
    response.body match {
      case Right(user) => 
        println(s"User created successfully: ID=${user.id}, Name=${user.name}")
        Right(user)
      case Left(error) => Left(s"Failed to create user: $error")
    }
  }
  
  // PUT request (update user)
  def updateUser(userId: Int, updateRequest: UserUpdateRequest): Either[String, User] = {
    val response = basicRequest
      .put(uri"https://api.example.com/users/$userId")
      .body(updateRequest)
      .response(asJson[User])
      .send(backend)
    
    response.body match {
      case Right(user) => 
        println(s"User updated successfully: ${user.name}")
        Right(user)
      case Left(error) => Left(s"Failed to update user: $error")
    }
  }
  
  // DELETE request
  def deleteUser(userId: Int): Either[String, Unit] = {
    val response = basicRequest
      .delete(uri"https://api.example.com/users/$userId")
      .send(backend)
    
    if (response.code.code == 204) {
      println(s"User deleted successfully: ID=$userId")
      Right(())
    } else {
      Left(s"Failed to delete user: ${response.code}")
    }
  }
  
  // Form data submission
  def submitForm(username: String, email: String, category: String): Either[String, String] = {
    val response = basicRequest
      .post(uri"https://api.example.com/register")
      .body(Map(
        "username" -> username,
        "email" -> email,
        "category" -> category
      ))
      .send(backend)
    
    response.body
  }
  
  // Usage example
  def main(args: Array[String]): Unit = {
    // Get user list
    getUserListWithParams(1, 10, "created_at") match {
      case Right(users) => users.foreach(user => println(s"User: ${user.name}"))
      case Left(error) => println(error)
    }
    
    // Create user
    val newUser = UserCreateRequest("John Doe", "[email protected]", 30)
    createUser(newUser) match {
      case Right(user) => println(s"Created user: ${user.id}")
      case Left(error) => println(error)
    }
    
    backend.close()
  }
}

Authentication and Security Configuration

import sttp.client4.*
import sttp.client4.wrappers.DigestAuthenticationBackend

object AuthenticationExamples {
  val backend = DefaultSyncBackend()
  
  // Basic authentication
  def basicAuthExample(): Unit = {
    val response = basicRequest
      .get(uri"https://api.example.com/protected")
      .auth.basic("username", "password")
      .send(backend)
    
    println(s"Basic auth response: ${response.body}")
  }
  
  // Bearer Token authentication
  def bearerTokenExample(): Unit = {
    val token = "your-jwt-token-here"
    
    val response = basicRequest
      .get(uri"https://api.example.com/user/profile")
      .auth.bearer(token)
      .send(backend)
    
    println(s"Bearer auth response: ${response.body}")
  }
  
  // API Key authentication
  def apiKeyExample(): Unit = {
    val apiKey = "your-api-key"
    
    val response = basicRequest
      .get(uri"https://api.example.com/data")
      .header("X-API-Key", apiKey)
      .header("X-Client-ID", "your-client-id")
      .send(backend)
    
    println(s"API key auth response: ${response.body}")
  }
  
  // OAuth 2.0 example
  case class OAuthTokenResponse(
    access_token: String,
    token_type: String,
    expires_in: Int,
    refresh_token: Option[String]
  )
  
  implicit val oauthDecoder: Decoder[OAuthTokenResponse] = deriveDecoder[OAuthTokenResponse]
  
  def oauth2Example(): Unit = {
    // Acquire access token
    val tokenResponse = basicRequest
      .post(uri"https://auth.example.com/oauth2/token")
      .auth.basic("client_id", "client_secret")
      .body(Map(
        "grant_type" -> "client_credentials",
        "scope" -> "read write"
      ))
      .response(asJson[OAuthTokenResponse])
      .send(backend)
    
    tokenResponse.body match {
      case Right(token) =>
        println(s"Token acquired successfully: ${token.access_token}")
        
        // API call with acquired token
        val apiResponse = basicRequest
          .get(uri"https://api.example.com/user/profile")
          .auth.bearer(token.access_token)
          .send(backend)
        
        println(s"API call result: ${apiResponse.body}")
        
      case Left(error) =>
        println(s"Token acquisition failed: $error")
    }
  }
  
  // Digest authentication
  def digestAuthExample(): Unit = {
    val digestBackend = DigestAuthenticationBackend(backend)
    
    val response = basicRequest
      .get(uri"https://api.example.com/digest-protected")
      .auth.digest("username", "password")
      .send(digestBackend)
    
    println(s"Digest auth response: ${response.body}")
  }
  
  // Custom authentication headers
  def customAuthExample(): Unit = {
    val timestamp = System.currentTimeMillis().toString
    val signature = generateSignature("api-key", "secret", timestamp)
    
    val response = basicRequest
      .get(uri"https://api.example.com/secure")
      .header("X-API-Key", "your-api-key")
      .header("X-Timestamp", timestamp)
      .header("X-Signature", signature)
      .send(backend)
    
    println(s"Custom auth response: ${response.body}")
  }
  
  def generateSignature(apiKey: String, secret: String, timestamp: String): String = {
    // Actual signature generation logic
    import java.security.MessageDigest
    val input = s"$apiKey$timestamp$secret"
    MessageDigest.getInstance("SHA-256").digest(input.getBytes).map("%02x".format(_)).mkString
  }
}

ZIO Integration and Functional Effects

import sttp.client4.*
import sttp.client4.httpclient.zio.HttpClientZioBackend
import zio.*
import io.circe.*
import io.circe.generic.semiauto.*
import sttp.client4.circe.*

object ZIOIntegrationExample extends ZIOAppDefault {
  
  case class User(id: Int, name: String, email: String)
  case class ApiError(code: Int, message: String)
  
  implicit val userDecoder: Decoder[User] = deriveDecoder[User]
  implicit val errorDecoder: Decoder[ApiError] = deriveDecoder[ApiError]
  
  // HTTP client service using ZIO
  trait UserService {
    def getUser(id: Int): Task[User]
    def createUser(name: String, email: String): Task[User]
    def getAllUsers(): Task[List[User]]
  }
  
  case class UserServiceImpl(backend: SttpBackend[Task, Any]) extends UserService {
    
    def getUser(id: Int): Task[User] = {
      basicRequest
        .get(uri"https://api.example.com/users/$id")
        .response(asJson[User])
        .send(backend)
        .flatMap { response =>
          response.body match {
            case Right(user) => ZIO.succeed(user)
            case Left(error) => ZIO.fail(new RuntimeException(s"Failed to get user: $error"))
          }
        }
    }
    
    def createUser(name: String, email: String): Task[User] = {
      val userData = Map("name" -> name, "email" -> email)
      
      basicRequest
        .post(uri"https://api.example.com/users")
        .body(userData)
        .response(asJson[User])
        .send(backend)
        .flatMap { response =>
          response.body match {
            case Right(user) => ZIO.succeed(user)
            case Left(error) => ZIO.fail(new RuntimeException(s"Failed to create user: $error"))
          }
        }
    }
    
    def getAllUsers(): Task[List[User]] = {
      basicRequest
        .get(uri"https://api.example.com/users")
        .response(asJson[List[User]])
        .send(backend)
        .flatMap { response =>
          response.body match {
            case Right(users) => ZIO.succeed(users)
            case Left(error) => ZIO.fail(new RuntimeException(s"Failed to get user list: $error"))
          }
        }
    }
  }
  
  // Layer definitions
  val userServiceLayer: ZLayer[SttpBackend[Task, Any], Nothing, UserService] =
    ZLayer.fromFunction(UserServiceImpl.apply _)
  
  val backendLayer: TaskLayer[SttpBackend[Task, Any]] =
    HttpClientZioBackend.layer()
  
  // Main program
  def run: ZIO[Any, Throwable, Unit] = {
    val program = for {
      userService <- ZIO.service[UserService]
      
      // Concurrent user retrieval
      users <- ZIO.collectAllPar(List(
        userService.getUser(1),
        userService.getUser(2),
        userService.getUser(3)
      )).catchAll { error =>
        ZIO.logError(s"User retrieval error: ${error.getMessage}") *>
        ZIO.succeed(List.empty[User])
      }
      
      _ <- ZIO.log(s"Retrieved user count: ${users.length}")
      
      // Create new user
      newUser <- userService.createUser("John Doe", "[email protected]").catchAll { error =>
        ZIO.logError(s"User creation error: ${error.getMessage}") *>
        ZIO.fail(error)
      }
      
      _ <- ZIO.log(s"Created user: ${newUser.name}")
      
      // Get all users
      allUsers <- userService.getAllUsers()
      _ <- ZIO.log(s"Total user count: ${allUsers.length}")
      
    } yield ()
    
    program.provide(
      userServiceLayer,
      backendLayer
    )
  }
}

Error Handling and Retry Functionality

import sttp.client4.*
import sttp.client4.wrappers.{TryBackend, EitherBackend}
import scala.util.{Try, Success, Failure}
import scala.concurrent.duration.*

object ErrorHandlingExamples {
  val backend = DefaultSyncBackend()
  
  // Try-based error handling
  def tryBasedHandling(): Unit = {
    val tryBackend = TryBackend(backend)
    
    val result: Try[Response[Either[String, String]]] = basicRequest
      .get(uri"https://api.example.com/unreliable")
      .send(tryBackend)
    
    result match {
      case Success(response) => 
        response.body match {
          case Right(content) => println(s"Success: $content")
          case Left(error) => println(s"Response error: $error")
        }
      case Failure(exception) => 
        println(s"Network error: ${exception.getMessage}")
    }
  }
  
  // Either-based error handling
  def eitherBasedHandling(): Unit = {
    val eitherBackend = EitherBackend(backend)
    
    val result: Either[Exception, Response[Either[String, String]]] = basicRequest
      .get(uri"https://api.example.com/data")
      .send(eitherBackend)
    
    result match {
      case Right(response) => 
        println(s"Response retrieved successfully: ${response.code}")
      case Left(exception) => 
        println(s"Request failed: ${exception.getMessage}")
    }
  }
  
  // Custom response handling
  case class ApiError(code: Int, message: String, details: String)
  implicit val apiErrorDecoder: Decoder[ApiError] = deriveDecoder[ApiError]
  
  def customResponseHandling(): Unit = {
    val response = basicRequest
      .get(uri"https://api.example.com/data")
      .response(asEither(asJson[ApiError], asJson[List[User]]))
      .send(backend)
    
    response.body match {
      case Right(Right(users)) => 
        println(s"Data retrieved successfully: ${users.length} items")
      case Right(Left(error)) => 
        println(s"API error: ${error.message} (code: ${error.code})")
      case Left(httpError) => 
        println(s"HTTP error: $httpError")
    }
  }
  
  // Retry mechanism implementation
  def retryableRequest[T](
    request: Request[T, Any], 
    maxRetries: Int = 3,
    backoffDelay: FiniteDuration = 1.second
  ): Either[String, Response[T]] = {
    
    def attempt(retriesLeft: Int): Either[String, Response[T]] = {
      val response = request.send(backend)
      
      if (response.code.code >= 500 && retriesLeft > 0) {
        println(s"Server error (${response.code}), retries left: $retriesLeft")
        Thread.sleep(backoffDelay.toMillis)
        attempt(retriesLeft - 1)
      } else if (response.code.code >= 400) {
        Left(s"Client error: ${response.code}")
      } else {
        Right(response)
      }
    }
    
    attempt(maxRetries)
  }
  
  // Circuit breaker implementation
  class CircuitBreaker(threshold: Int, timeout: FiniteDuration) {
    private var failureCount = 0
    private var lastFailureTime: Long = 0
    private var state: String = "closed" // "closed", "open", "half-open"
    
    def execute[T](request: Request[T, Any]): Either[String, Response[T]] = {
      state match {
        case "open" =>
          if (System.currentTimeMillis() - lastFailureTime > timeout.toMillis) {
            state = "half-open"
            println("Circuit breaker: half-open state")
            executeRequest(request)
          } else {
            Left("Circuit breaker is open")
          }
        case _ =>
          executeRequest(request)
      }
    }
    
    private def executeRequest[T](request: Request[T, Any]): Either[String, Response[T]] = {
      val response = request.send(backend)
      
      if (response.code.code >= 500) {
        failureCount += 1
        lastFailureTime = System.currentTimeMillis()
        
        if (failureCount >= threshold) {
          state = "open"
          println(s"Circuit breaker opened (failure count: $failureCount)")
        }
        
        Left(s"Server error: ${response.code}")
      } else {
        // Reset on success
        failureCount = 0
        state = "closed"
        Right(response)
      }
    }
  }
  
  def circuitBreakerExample(): Unit = {
    val circuitBreaker = new CircuitBreaker(3, 30.seconds)
    
    for (i <- 1 to 10) {
      val result = circuitBreaker.execute(
        basicRequest.get(uri"https://api.example.com/unreliable")
      )
      
      result match {
        case Right(response) => 
          println(s"Attempt $i successful: ${response.code}")
        case Left(error) => 
          println(s"Attempt $i failed: $error")
      }
      
      Thread.sleep(1000)
    }
  }
}

File Operations and Advanced Features

import sttp.client4.*
import java.io.File
import java.nio.file.{Files, Paths}

object FileOperationsExamples {
  val backend = DefaultSyncBackend()
  
  // File upload
  def uploadFile(): Unit = {
    val file = new File("/path/to/document.pdf")
    
    val response = basicRequest
      .post(uri"https://api.example.com/upload")
      .multipartBody(
        multipart("file", file).fileName("document.pdf").contentType("application/pdf"),
        multipart("description", "Important document"),
        multipart("category", "legal")
      )
      .header("Authorization", "Bearer your-token")
      .send(backend)
    
    response.body match {
      case Right(result) => println(s"Upload successful: $result")
      case Left(error) => println(s"Upload failed: $error")
    }
  }
  
  // Multiple file upload
  def uploadMultipleFiles(): Unit = {
    val document = new File("/path/to/document.pdf")
    val image = new File("/path/to/image.jpg")
    val data = new File("/path/to/data.json")
    
    val response = basicRequest
      .post(uri"https://api.example.com/upload/batch")
      .multipartBody(
        multipart("title", "Batch upload"),
        multipart("description", "Multiple file batch processing"),
        multipart("document", document).fileName("doc.pdf"),
        multipart("image", image).fileName("image.jpg"),
        multipart("data", data).fileName("data.json")
      )
      .send(backend)
    
    println(s"Batch upload result: ${response.body}")
  }
  
  // File download
  def downloadFile(): Unit = {
    val downloadPath = "/path/to/downloads/downloaded_file.pdf"
    
    val response = basicRequest
      .get(uri"https://api.example.com/files/document.pdf")
      .response(asFile(new File(downloadPath)))
      .send(backend)
    
    response.body match {
      case Right(file) => 
        println(s"Download completed: ${file.getAbsolutePath}")
        println(s"File size: ${file.length()} bytes")
      case Left(error) => 
        println(s"Download failed: $error")
    }
  }
  
  // Streaming download (for large files)
  def streamingDownload(): Unit = {
    val response = basicRequest
      .get(uri"https://api.example.com/large-file.zip")
      .response(asStream[InputStream])
      .send(backend)
    
    response.body match {
      case Right(inputStream) =>
        val outputPath = Paths.get("/path/to/downloads/large-file.zip")
        Files.copy(inputStream, outputPath)
        inputStream.close()
        println(s"Streaming download completed: $outputPath")
        
      case Left(error) =>
        println(s"Streaming download failed: $error")
    }
  }
  
  // Binary data handling
  def binaryDataHandling(): Unit = {
    val binaryData: Array[Byte] = Files.readAllBytes(Paths.get("/path/to/binary.dat"))
    
    // Binary data upload
    val uploadResponse = basicRequest
      .post(uri"https://api.example.com/binary")
      .body(binaryData)
      .header("Content-Type", "application/octet-stream")
      .send(backend)
    
    // Binary data download
    val downloadResponse = basicRequest
      .get(uri"https://api.example.com/binary/12345")
      .response(asByteArray)
      .send(backend)
    
    downloadResponse.body match {
      case Right(bytes) =>
        Files.write(Paths.get("/path/to/received_binary.dat"), bytes)
        println(s"Binary data received: ${bytes.length} bytes")
      case Left(error) =>
        println(s"Binary data reception failed: $error")
    }
  }
  
  // WebSocket connection example
  def webSocketExample(): Unit = {
    import sttp.client4.httpclient.HttpClientSyncBackend
    import sttp.ws.WebSocket
    
    val wsBackend = HttpClientSyncBackend()
    
    val response = basicRequest
      .get(uri"wss://echo.websocket.org")
      .response(asWebSocket { ws: WebSocket[Identity] =>
        // Send message
        ws.sendText("Hello WebSocket!")
        
        // Receive message
        val received = ws.receiveText()
        println(s"Received message: $received")
        
        // Close WebSocket
        ws.close()
        
        received.getOrElse("")
      })
      .send(wsBackend)
    
    response.body match {
      case Right(result) => println(s"WebSocket communication result: $result")
      case Left(error) => println(s"WebSocket connection failed: $error")
    }
    
    wsBackend.close()
  }
}

Performance Optimization and Monitoring

import sttp.client4.*
import sttp.client4.logging.LoggingBackend
import sttp.client4.wrappers.FollowRedirectsBackend
import scala.concurrent.duration.*

object PerformanceOptimizationExamples {
  
  // Optimized backend configuration
  def createOptimizedBackend(): SttpBackend[Identity, Any] = {
    val baseBackend = DefaultSyncBackend(
      options = SttpBackendOptions(
        connectionTimeout = 30.seconds,
        proxy = None
      )
    )
    
    // Backend with logging functionality
    val loggingBackend = LoggingBackend(
      delegate = baseBackend,
      logRequestBody = true,
      logResponseBody = true
    )
    
    // Redirect support
    FollowRedirectsBackend(loggingBackend)
  }
  
  // Connection pool optimization
  def connectionPoolOptimization(): Unit = {
    import java.net.http.HttpClient
    import java.time.Duration
    
    val httpClient = HttpClient.newBuilder()
      .connectTimeout(Duration.ofSeconds(30))
      .version(HttpClient.Version.HTTP_2)  // HTTP/2 preferred
      .followRedirects(HttpClient.Redirect.NORMAL)
      .build()
    
    val backend = HttpClientSyncBackend.usingClient(httpClient)
    
    // Test request for configuration verification
    val response = basicRequest
      .get(uri"https://httpbin.org/get")
      .send(backend)
    
    println(s"HTTP version: ${response.httpVersion}")
    println(s"Response time: ${response.responseTime}")
    
    backend.close()
  }
  
  // Concurrent request processing
  def concurrentRequests(): Unit = {
    import scala.concurrent.{Future, ExecutionContext}
    import scala.concurrent.duration.*
    import scala.util.{Success, Failure}
    
    implicit val ec: ExecutionContext = ExecutionContext.global
    val backend = DefaultFutureBackend()
    
    val userIds = (1 to 20).toList
    
    // Concurrent user information retrieval
    val futures = userIds.map { id =>
      basicRequest
        .get(uri"https://api.example.com/users/$id")
        .response(asJson[User])
        .send(backend)
        .map { response =>
          (id, response.body)
        }
    }
    
    // Wait for all responses
    val startTime = System.currentTimeMillis()
    
    Future.sequence(futures).onComplete {
      case Success(results) =>
        val endTime = System.currentTimeMillis()
        val successCount = results.count(_._2.isRight)
        
        println(s"Concurrent processing completed: ${endTime - startTime}ms")
        println(s"Success: $successCount / ${results.length}")
        
        backend.close()
        
      case Failure(exception) =>
        println(s"Concurrent processing failed: ${exception.getMessage}")
        backend.close()
    }
  }
  
  // Request monitoring and metrics
  def requestMonitoring(): Unit = {
    var requestCount = 0
    var totalResponseTime = 0L
    
    val monitoringBackend = new SttpBackend[Identity, Any] {
      def send[T](request: Request[T, Any]): Response[T] = {
        val startTime = System.currentTimeMillis()
        requestCount += 1
        
        println(s"Request #$requestCount: ${request.method} ${request.uri}")
        
        val response = DefaultSyncBackend().send(request)
        
        val responseTime = System.currentTimeMillis() - startTime
        totalResponseTime += responseTime
        
        println(s"Response #$requestCount: ${response.code} (${responseTime}ms)")
        println(s"Average response time: ${totalResponseTime / requestCount}ms")
        
        response
      }
      
      def close(): Unit = DefaultSyncBackend().close()
      def responseMonad: MonadError[Identity] = IdMonad
    }
    
    // Execute requests with monitoring functionality
    val response1 = basicRequest.get(uri"https://httpbin.org/get").send(monitoringBackend)
    val response2 = basicRequest.get(uri"https://httpbin.org/status/200").send(monitoringBackend)
    val response3 = basicRequest.get(uri"https://httpbin.org/delay/1").send(monitoringBackend)
    
    println(s"Total requests: $requestCount")
    println(s"Average response time: ${totalResponseTime / requestCount}ms")
    
    monitoringBackend.close()
  }
  
  // Caching functionality implementation
  class CachingBackend(delegate: SttpBackend[Identity, Any]) extends SttpBackend[Identity, Any] {
    private val cache = scala.collection.mutable.Map[String, (Response[String], Long)]()
    private val cacheTimeout = 60000 // 1 minute
    
    def send[T](request: Request[T, Any]): Response[T] = {
      val cacheKey = s"${request.method}:${request.uri}"
      val currentTime = System.currentTimeMillis()
      
      // Only cache GET requests
      if (request.method.method == "GET") {
        cache.get(cacheKey) match {
          case Some((cachedResponse, timestamp)) if currentTime - timestamp < cacheTimeout =>
            println(s"Cache hit: $cacheKey")
            cachedResponse.asInstanceOf[Response[T]]
          case _ =>
            println(s"Cache miss: $cacheKey")
            val response = delegate.send(request)
            if (response.code.code == 200) {
              cache.put(cacheKey, (response.asInstanceOf[Response[String]], currentTime))
            }
            response
        }
      } else {
        delegate.send(request)
      }
    }
    
    def close(): Unit = {
      cache.clear()
      delegate.close()
    }
    
    def responseMonad: MonadError[Identity] = delegate.responseMonad
  }
  
  def cachingExample(): Unit = {
    val cachingBackend = new CachingBackend(DefaultSyncBackend())
    
    // Execute same request multiple times
    val url = uri"https://httpbin.org/get"
    
    println("First request:")
    val response1 = basicRequest.get(url).send(cachingBackend)
    
    println("Second request (from cache):")
    val response2 = basicRequest.get(url).send(cachingBackend)
    
    println("Third request (from cache):")
    val response3 = basicRequest.get(url).send(cachingBackend)
    
    cachingBackend.close()
  }
}

Practical Usage Patterns

API Integration Service Construction

import sttp.client4.*
import zio.*
import io.circe.*
import io.circe.generic.semiauto.*

// Actual API service integration example
case class GitHubRepository(
  id: Long,
  name: String,
  full_name: String,
  description: Option[String],
  stargazers_count: Int,
  language: Option[String]
)

case class SearchResult(
  total_count: Int,
  items: List[GitHubRepository]
)

implicit val repoDecoder: Decoder[GitHubRepository] = deriveDecoder[GitHubRepository]
implicit val searchDecoder: Decoder[SearchResult] = deriveDecoder[SearchResult]

class GitHubApiService(backend: SttpBackend[Task, Any]) {
  private val baseUrl = uri"https://api.github.com"
  
  def searchRepositories(query: String, sort: String = "stars", per_page: Int = 30): Task[SearchResult] = {
    basicRequest
      .get(uri"$baseUrl/search/repositories?q=$query&sort=$sort&per_page=$per_page")
      .header("Accept", "application/vnd.github.v3+json")
      .header("User-Agent", "sttp-example/1.0")
      .response(asJson[SearchResult])
      .send(backend)
      .flatMap { response =>
        response.body match {
          case Right(result) => ZIO.succeed(result)
          case Left(error) => ZIO.fail(new RuntimeException(s"GitHub API error: $error"))
        }
      }
  }
  
  def getRepository(owner: String, repo: String): Task[GitHubRepository] = {
    basicRequest
      .get(uri"$baseUrl/repos/$owner/$repo")
      .response(asJson[GitHubRepository])
      .send(backend)
      .flatMap { response =>
        response.body match {
          case Right(repository) => ZIO.succeed(repository)
          case Left(error) => ZIO.fail(new RuntimeException(s"Repository not found: $error"))
        }
      }
  }
}

// Usage example
object GitHubServiceExample extends ZIOAppDefault {
  def run = {
    val program = for {
      backend <- ZIO.service[SttpBackend[Task, Any]]
      githubService = new GitHubApiService(backend)
      
      // Search Scala projects
      scalaRepos <- githubService.searchRepositories("language:scala", "stars", 10)
      _ <- ZIO.log(s"Found ${scalaRepos.total_count} Scala repositories")
      
      topRepos = scalaRepos.items.take(5)
      _ <- ZIO.foreach(topRepos) { repo =>
        ZIO.log(s"${repo.name}: ${repo.stargazers_count} stars")
      }
      
      // Get specific repository details
      sttpRepo <- githubService.getRepository("softwaremill", "sttp")
      _ <- ZIO.log(s"sttp repository: ${sttpRepo.description.getOrElse("No description")}")
      
    } yield ()
    
    program.provide(
      HttpClientZioBackend.layer()
    )
  }
}