diff --git a/bin/configs/scala-sttp4-petstore-new.yaml b/bin/configs/scala-sttp4-petstore-new.yaml
new file mode 100644
index 00000000000..6f8f95ec491
--- /dev/null
+++ b/bin/configs/scala-sttp4-petstore-new.yaml
@@ -0,0 +1,6 @@
+generatorName: scala-sttp4
+outputDir: samples/client/petstore/scala/sttp4
+inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
+templateDir: modules/openapi-generator/src/main/resources/scala-sttp4
+additionalProperties:
+ hideGenerationTimestamp: "true"
diff --git a/docs/generators.md b/docs/generators.md
index 7bc9790541d..c1adb82b6d6 100644
--- a/docs/generators.md
+++ b/docs/generators.md
@@ -58,6 +58,7 @@ The following generators are available:
* [scala-akka](generators/scala-akka.md)
* [scala-gatling](generators/scala-gatling.md)
* [scala-sttp](generators/scala-sttp.md)
+* [scala-sttp4 (beta)](generators/scala-sttp4.md)
* [scalaz](generators/scalaz.md)
* [swift-combine](generators/swift-combine.md)
* [swift5](generators/swift5.md)
diff --git a/docs/generators/scala-sttp4.md b/docs/generators/scala-sttp4.md
new file mode 100644
index 00000000000..77e9c995bcb
--- /dev/null
+++ b/docs/generators/scala-sttp4.md
@@ -0,0 +1,252 @@
+---
+title: Documentation for the scala-sttp4 Generator
+---
+
+## METADATA
+
+| Property | Value | Notes |
+| -------- | ----- | ----- |
+| generator name | scala-sttp4 | pass this to the generate command after -g |
+| generator stability | BETA | |
+| generator type | CLIENT | |
+| generator language | Scala | |
+| generator default templating engine | mustache | |
+| helpTxt | Generates a Scala client library (beta) based on Sttp4. | |
+
+## CONFIG OPTIONS
+These options may be applied as additional-properties (cli) or configOptions (plugins). Refer to [configuration docs](https://openapi-generator.tech/docs/configuration) for more details.
+
+| Option | Description | Values | Default |
+| ------ | ----------- | ------ | ------- |
+|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
+|apiPackage|package for generated api classes| |null|
+|dateLibrary|Option. Date library to use|
- **joda**
- Joda (for legacy app)
- **java8**
- Java 8 native JSR310 (preferred for JDK 1.8+)
|java8|
+|disallowAdditionalPropertiesIfNotPresent|If false, the 'additionalProperties' implementation (set to true by default) is compliant with the OAS and JSON schema specifications. If true (default), keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.|- **false**
- The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.
- **true**
- Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.
|true|
+|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
+|enumUnknownDefaultCase|If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response.With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the server sends an enum case that is not known by the client/spec, they can safely fallback to this case.|- **false**
- No changes to the enum's are made, this is the default option.
- **true**
- With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the enum case sent by the server is not known by the client/spec, can safely be decoded to this case.
|false|
+|jodaTimeVersion|The version of joda-time library| |2.10.13|
+|json4sVersion|The version of json4s library| |4.0.6|
+|jsonLibrary|Json library to use. Possible values are: json4s and circe.| |json4s|
+|legacyDiscriminatorBehavior|Set to false for generators with better support for discriminators. (Python, Java, Go, PowerShell, C# have this enabled by default).|- **true**
- The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.
- **false**
- The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.
|true|
+|mainPackage|Top-level package name, which defines 'apiPackage', 'modelPackage', 'invokerPackage'| |org.openapitools.client|
+|modelPackage|package for generated models| |null|
+|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |camelCase|
+|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
+|separateErrorChannel|Whether to return response as F[Either[ResponseError[ErrorType], ReturnType]]] or to flatten response's error raising them through enclosing monad (F[ReturnType]).| |true|
+|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
+|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
+|sourceFolder|source folder for generated code| |null|
+|sttpClientVersion|The version of sttp client| |4.0.0-M1|
+
+## IMPORT MAPPING
+
+| Type/Alias | Imports |
+| ---------- | ------- |
+|Array|java.util.List|
+|ArrayList|java.util.ArrayList|
+|Date|java.util.Date|
+|DateTime|org.joda.time.*|
+|File|java.io.File|
+|HashMap|java.util.HashMap|
+|ListBuffer|scala.collection.mutable.ListBuffer|
+|ListSet|scala.collection.immutable.ListSet|
+|LocalDate|org.joda.time.*|
+|LocalDateTime|org.joda.time.*|
+|LocalTime|org.joda.time.*|
+|Seq|scala.collection.immutable.Seq|
+|Set|scala.collection.immutable.Set|
+|Timestamp|java.sql.Timestamp|
+|URI|java.net.URI|
+|UUID|java.util.UUID|
+
+
+## INSTANTIATION TYPES
+
+| Type/Alias | Instantiated By |
+| ---------- | --------------- |
+|array|ListBuffer|
+|map|Map|
+|set|Set|
+
+
+## LANGUAGE PRIMITIVES
+
+
+- Any
+- Array
+- Boolean
+- Byte
+- Double
+- Float
+- Int
+- List
+- Long
+- Map
+- Object
+- Seq
+- String
+- boolean
+
+
+## RESERVED WORDS
+
+
+- abstract
+- case
+- catch
+- class
+- clone
+- def
+- do
+- else
+- extends
+- false
+- final
+- finally
+- for
+- forSome
+- if
+- implicit
+- import
+- lazy
+- match
+- new
+- null
+- object
+- override
+- package
+- private
+- protected
+- return
+- sealed
+- super
+- this
+- throw
+- trait
+- true
+- try
+- type
+- val
+- var
+- while
+- with
+- yield
+
+
+## FEATURE SET
+
+
+### Client Modification Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|BasePath|✓|ToolingExtension
+|Authorizations|✗|ToolingExtension
+|UserAgent|✓|ToolingExtension
+|MockServer|✗|ToolingExtension
+
+### Data Type Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Custom|✗|OAS2,OAS3
+|Int32|✓|OAS2,OAS3
+|Int64|✓|OAS2,OAS3
+|Float|✓|OAS2,OAS3
+|Double|✓|OAS2,OAS3
+|Decimal|✓|ToolingExtension
+|String|✓|OAS2,OAS3
+|Byte|✓|OAS2,OAS3
+|Binary|✓|OAS2,OAS3
+|Boolean|✓|OAS2,OAS3
+|Date|✓|OAS2,OAS3
+|DateTime|✓|OAS2,OAS3
+|Password|✓|OAS2,OAS3
+|File|✓|OAS2
+|Uuid|✗|
+|Array|✓|OAS2,OAS3
+|Null|✗|OAS3
+|AnyType|✗|OAS2,OAS3
+|Object|✓|OAS2,OAS3
+|Maps|✓|ToolingExtension
+|CollectionFormat|✓|OAS2
+|CollectionFormatMulti|✓|OAS2
+|Enum|✓|OAS2,OAS3
+|ArrayOfEnum|✓|ToolingExtension
+|ArrayOfModel|✓|ToolingExtension
+|ArrayOfCollectionOfPrimitives|✓|ToolingExtension
+|ArrayOfCollectionOfModel|✓|ToolingExtension
+|ArrayOfCollectionOfEnum|✓|ToolingExtension
+|MapOfEnum|✓|ToolingExtension
+|MapOfModel|✓|ToolingExtension
+|MapOfCollectionOfPrimitives|✓|ToolingExtension
+|MapOfCollectionOfModel|✓|ToolingExtension
+|MapOfCollectionOfEnum|✓|ToolingExtension
+
+### Documentation Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Readme|✓|ToolingExtension
+|Model|✓|ToolingExtension
+|Api|✓|ToolingExtension
+
+### Global Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Host|✓|OAS2,OAS3
+|BasePath|✓|OAS2,OAS3
+|Info|✓|OAS2,OAS3
+|Schemes|✗|OAS2,OAS3
+|PartialSchemes|✓|OAS2,OAS3
+|Consumes|✓|OAS2
+|Produces|✓|OAS2
+|ExternalDocumentation|✓|OAS2,OAS3
+|Examples|✓|OAS2,OAS3
+|XMLStructureDefinitions|✗|OAS2,OAS3
+|MultiServer|✗|OAS3
+|ParameterizedServer|✗|OAS3
+|ParameterStyling|✗|OAS3
+|Callbacks|✗|OAS3
+|LinkObjects|✗|OAS3
+
+### Parameter Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Path|✓|OAS2,OAS3
+|Query|✓|OAS2,OAS3
+|Header|✓|OAS2,OAS3
+|Body|✓|OAS2
+|FormUnencoded|✓|OAS2
+|FormMultipart|✓|OAS2
+|Cookie|✗|OAS3
+
+### Schema Support Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Simple|✓|OAS2,OAS3
+|Composite|✓|OAS2,OAS3
+|Polymorphism|✗|OAS2,OAS3
+|Union|✗|OAS3
+|allOf|✗|OAS2,OAS3
+|anyOf|✗|OAS3
+|oneOf|✗|OAS3
+|not|✗|OAS3
+
+### Security Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|BasicAuth|✓|OAS2,OAS3
+|ApiKey|✓|OAS2,OAS3
+|OpenIDConnect|✗|OAS3
+|BearerToken|✓|OAS3
+|OAuth2_Implicit|✗|OAS2,OAS3
+|OAuth2_Password|✗|OAS2,OAS3
+|OAuth2_ClientCredentials|✗|OAS2,OAS3
+|OAuth2_AuthorizationCode|✗|OAS2,OAS3
+|SignatureAuth|✗|OAS3
+
+### Wire Format Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|JSON|✓|OAS2,OAS3
+|XML|✓|OAS2,OAS3
+|PROTOBUF|✗|ToolingExtension
+|Custom|✓|OAS2,OAS3
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttp4ClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttp4ClientCodegen.java
new file mode 100644
index 00000000000..3dd5def7810
--- /dev/null
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttp4ClientCodegen.java
@@ -0,0 +1,605 @@
+package org.openapitools.codegen.languages;
+
+import com.samskivert.mustache.Mustache;
+import com.samskivert.mustache.Template;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.media.ArraySchema;
+import io.swagger.v3.oas.models.media.Schema;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import io.swagger.v3.oas.models.servers.Server;
+import org.apache.commons.lang3.StringUtils;
+import org.openapitools.codegen.*;
+import org.openapitools.codegen.meta.GeneratorMetadata;
+import org.openapitools.codegen.meta.Stability;
+import org.openapitools.codegen.meta.features.*;
+import org.openapitools.codegen.model.ModelMap;
+import org.openapitools.codegen.model.ModelsMap;
+import org.openapitools.codegen.model.OperationMap;
+import org.openapitools.codegen.model.OperationsMap;
+import org.openapitools.codegen.utils.CamelizeOption;
+import org.openapitools.codegen.utils.ModelUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.openapitools.codegen.utils.CamelizeOption.*;
+import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER;
+import static org.openapitools.codegen.utils.StringUtils.camelize;
+
+public class ScalaSttp4ClientCodegen extends AbstractScalaCodegen implements CodegenConfig {
+ private static final StringProperty STTP_CLIENT_VERSION = new StringProperty("sttpClientVersion", "The version of " +
+ "sttp client", "4.0.0-M1");
+ private static final BooleanProperty USE_SEPARATE_ERROR_CHANNEL = new BooleanProperty("separateErrorChannel",
+ "Whether to return response as " +
+ "F[Either[ResponseError[ErrorType], ReturnType]]] or to flatten " +
+ "response's error raising them through enclosing monad (F[ReturnType]).", true);
+ private static final StringProperty JODA_TIME_VERSION = new StringProperty("jodaTimeVersion", "The version of " +
+ "joda-time library", "2.10.13");
+ private static final StringProperty JSON4S_VERSION = new StringProperty("json4sVersion", "The version of json4s " +
+ "library", "4.0.6");
+
+ private static final JsonLibraryProperty JSON_LIBRARY_PROPERTY = new JsonLibraryProperty();
+
+ public static final String DEFAULT_PACKAGE_NAME = "org.openapitools.client";
+ private static final PackageProperty PACKAGE_PROPERTY = new PackageProperty();
+
+ private static final List> properties = Arrays.asList(
+ STTP_CLIENT_VERSION, USE_SEPARATE_ERROR_CHANNEL, JODA_TIME_VERSION,
+ JSON4S_VERSION, JSON_LIBRARY_PROPERTY, PACKAGE_PROPERTY);
+
+ private final Logger LOGGER = LoggerFactory.getLogger(ScalaSttp4ClientCodegen.class);
+
+ protected String groupId = "org.openapitools";
+ protected String artifactId = "openapi-client";
+ protected String artifactVersion = "1.0.0";
+ protected boolean registerNonStandardStatusCodes = true;
+ protected boolean renderJavadoc = true;
+ protected boolean removeOAuthSecurities = true;
+
+ Map enumRefs = new HashMap<>();
+
+ public ScalaSttp4ClientCodegen() {
+ super();
+ generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
+ .stability(Stability.BETA)
+ .build();
+
+ modifyFeatureSet(features -> features
+ .includeDocumentationFeatures(DocumentationFeature.Readme)
+ .wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON, WireFormatFeature.XML, WireFormatFeature.Custom))
+ .securityFeatures(EnumSet.of(
+ SecurityFeature.BasicAuth,
+ SecurityFeature.ApiKey,
+ SecurityFeature.BearerToken
+ ))
+ .excludeGlobalFeatures(
+ GlobalFeature.XMLStructureDefinitions,
+ GlobalFeature.Callbacks,
+ GlobalFeature.LinkObjects,
+ GlobalFeature.ParameterStyling
+ )
+ .excludeSchemaSupportFeatures(
+ SchemaSupportFeature.Polymorphism
+ )
+ .excludeParameterFeatures(
+ ParameterFeature.Cookie
+ )
+ .includeClientModificationFeatures(
+ ClientModificationFeature.BasePath,
+ ClientModificationFeature.UserAgent
+ )
+ );
+
+ outputFolder = "generated-code/scala-sttp4";
+ modelTemplateFiles.put("model.mustache", ".scala");
+ apiTemplateFiles.put("api.mustache", ".scala");
+ embeddedTemplateDir = templateDir = "scala-sttp4";
+
+ String jsonLibrary = JSON_LIBRARY_PROPERTY.getValue(additionalProperties);
+
+ String jsonValueClass = "circe".equals(jsonLibrary) ? "io.circe.Json" : "org.json4s.JValue";
+
+ additionalProperties.put(CodegenConstants.GROUP_ID, groupId);
+ additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId);
+ additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion);
+ if (renderJavadoc) {
+ additionalProperties.put("javadocRenderer", new JavadocLambda());
+ }
+ additionalProperties.put("fnCapitalize", new CapitalizeLambda());
+ additionalProperties.put("fnCamelize", new CamelizeLambda(false));
+ additionalProperties.put("fnEnumEntry", new EnumEntryLambda());
+
+// importMapping.remove("Seq");
+// importMapping.remove("List");
+// importMapping.remove("Set");
+// importMapping.remove("Map");
+
+ // TODO: there is no specific sttp mapping. All Scala Type mappings should be in AbstractScala
+ typeMapping = new HashMap<>();
+ typeMapping.put("array", "Seq");
+ typeMapping.put("set", "Set");
+ typeMapping.put("boolean", "Boolean");
+ typeMapping.put("string", "String");
+ typeMapping.put("int", "Int");
+ typeMapping.put("integer", "Int");
+ typeMapping.put("long", "Long");
+ typeMapping.put("float", "Float");
+ typeMapping.put("byte", "Byte");
+ typeMapping.put("short", "Short");
+ typeMapping.put("char", "Char");
+ typeMapping.put("double", "Double");
+ typeMapping.put("object", "Any");
+ typeMapping.put("file", "File");
+ typeMapping.put("binary", "File");
+ typeMapping.put("number", "Double");
+ typeMapping.put("decimal", "BigDecimal");
+ typeMapping.put("ByteArray", "Array[Byte]");
+ typeMapping.put("AnyType", jsonValueClass);
+
+ instantiationTypes.put("array", "ListBuffer");
+ instantiationTypes.put("map", "Map");
+
+ properties.stream()
+ .map(Property::toCliOptions)
+ .flatMap(Collection::stream)
+ .forEach(option -> cliOptions.add(option));
+ }
+
+ @Override
+ public void processOpts() {
+ super.processOpts();
+ properties.forEach(p -> p.updateAdditionalProperties(additionalProperties));
+ invokerPackage = PACKAGE_PROPERTY.getInvokerPackage(additionalProperties);
+ apiPackage = PACKAGE_PROPERTY.getApiPackage(additionalProperties);
+ modelPackage = PACKAGE_PROPERTY.getModelPackage(additionalProperties);
+
+ supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
+ supportingFiles.add(new SupportingFile("build.sbt.mustache", "", "build.sbt"));
+ final String invokerFolder = (sourceFolder + File.separator + invokerPackage).replace(".", File.separator);
+ supportingFiles.add(new SupportingFile("jsonSupport.mustache", invokerFolder, "JsonSupport.scala"));
+ supportingFiles.add(new SupportingFile("additionalTypeSerializers.mustache", invokerFolder, "AdditionalTypeSerializers.scala"));
+ supportingFiles.add(new SupportingFile("project/build.properties.mustache", "project", "build.properties"));
+ supportingFiles.add(new SupportingFile("dateSerializers.mustache", invokerFolder, "DateSerializers.scala"));
+ }
+
+ @Override
+ public String getName() {
+ return "scala-sttp4";
+ }
+
+ @Override
+ public String getHelp() {
+ return "Generates a Scala client library (beta) based on Sttp4.";
+ }
+
+ @Override
+ public String encodePath(String input) {
+ String path = super.encodePath(input);
+
+ // The parameter names in the URI must be converted to the same case as
+ // the method parameter.
+ StringBuffer buf = new StringBuffer(path.length());
+ Matcher matcher = Pattern.compile("[{](.*?)[}]").matcher(path);
+ while (matcher.find()) {
+ matcher.appendReplacement(buf, "\\${" + toParamName(matcher.group(0)) + "}");
+ }
+ matcher.appendTail(buf);
+ return buf.toString();
+ }
+
+ @Override
+ public CodegenOperation fromOperation(String path,
+ String httpMethod,
+ Operation operation,
+ List servers) {
+ CodegenOperation op = super.fromOperation(path, httpMethod, operation, servers);
+ op.path = encodePath(path);
+ return op;
+ }
+
+ @Override
+ public CodegenType getTag() {
+ return CodegenType.CLIENT;
+ }
+
+ @Override
+ public String escapeReservedWord(String name) {
+ if (this.reservedWordsMappings().containsKey(name)) {
+ return this.reservedWordsMappings().get(name);
+ }
+ return "`" + name + "`";
+ }
+
+ @Override
+ public ModelsMap postProcessModels(ModelsMap objs) {
+ return objs;
+ }
+
+ /**
+ * Invoked by {@link DefaultGenerator} after all models have been post-processed,
+ * allowing for a last pass of codegen-specific model cleanup.
+ *
+ * @param objs Current state of codegen object model.
+ * @return An in-place modified state of the codegen object model.
+ */
+ @Override
+ public Map postProcessAllModels(Map objs) {
+ final Map processed = super.postProcessAllModels(objs);
+ postProcessUpdateImports(processed);
+ return processed;
+ }
+
+ /**
+ * Update/clean up model imports
+ *
+ * append '._" if the import is a Enum class, otherwise
+ * remove model imports to avoid warnings for importing class in the same package in Scala
+ *
+ * @param models processed models to be further processed
+ */
+ @SuppressWarnings("unchecked")
+ private void postProcessUpdateImports(final Map models) {
+ final String prefix = modelPackage() + ".";
+
+ enumRefs = getEnumRefs(models);
+
+ for (String openAPIName : models.keySet()) {
+ CodegenModel model = ModelUtils.getModelByName(openAPIName, models);
+ if (model == null) {
+ LOGGER.warn("Expected to retrieve model {} by name, but no model was found. Check your -Dmodels inclusions.", openAPIName);
+ continue;
+ }
+
+ ModelsMap objs = models.get(openAPIName);
+ List