openapi-generator icon indicating copy to clipboard operation
openapi-generator copied to clipboard

[BUG] java.util.Locale is mixed with custom component if named "Locale"

Open ahoehma opened this issue 2 months ago • 8 comments

Description

Since 7.15.0 I have compiler errror for the generated code for my openapi spec. The spec contains a custom component "Locale" ... in the past this was working fine.

openapi-generator version

7.16.0+

OpenAPI declaration file content or url

If you post the code inline, please wrap it with

openapi: 3.0.1
info:
  version: '1.27'
  title: Example to show effects with Locale
  description: This definition was taken from a real world api which was working until 7.15.0.
paths:
  /api/v1/brain/product/localizations/{productId}/{version}:
    post:
      operationId: getProductLocalizations
      summary: Get localization data for a product.
      parameters:
        - name: productId
          in: path
          description: Provide the id of the product.
          required: true
          schema:
            type: string
            minLength: 1
          example: 1LE1X
        - name: version
          in: path
          description: Provide the version of the product. Could be 'LATEST' to retrieve the newest version.
          required: true
          schema:
            type: string
            minLength: 1
          example: LATEST
      requestBody:
        description: Required payload for the request.
        required: true
        content:
          application/json:
            examples:
              0 - Only language 'en':
                description: Language 'en' without country.
                summary: Only language 'en'
                value:
                  locales:
                    - language: en
              1 - Multiple locales:
                description: Locale 'EN_en' and 'DE_de'.
                summary: Multiple locales
                value:
                  locales:
                    - country: EN
                      language: en
                    - country: DE
                      language: de
            schema:
              $ref: '#/components/schemas/GetProductLocalizationsPayload'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetProductLocalizationsResult'
        '400':
          description: Bad Request
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetail'
        '401':
          description: Unauthorized
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetail'
        '403':
          description: Forbidden
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetail'
        '404':
          description: Not found
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetail'
        '500':
          description: Internal Server Error
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetail'
      security:
        - basicScheme: []
      tags:
        - service
        - read
        - v1.24
components:
  schemas:
    CsticLocalization:
      description: Localization data of a Cstic.
      type: object
      properties:
        longText:
          type: string
        name:
          type: string
        numberPattern:
          type: string
        taggedTexts:
          $ref: '#/components/schemas/TaggedTexts'
        text:
          type: string
        unit:
          type: string
        values:
          type: array
          items:
            $ref: '#/components/schemas/Localization'
    ElementLocalization:
      description: Localization data of an Element.
      type: object
      properties:
        cstics:
          type: array
          items:
            $ref: '#/components/schemas/CsticLocalization'
        longText:
          type: string
        name:
          type: string
        taggedTexts:
          $ref: '#/components/schemas/TaggedTexts'
        text:
          type: string
    GetProductLocalizationsPayload:
      description: Container for locales.
      type: object
      properties:
        locales:
          type: array
          items:
            $ref: '#/components/schemas/Locale'
    GetProductLocalizationsResult:
      description: Result for product localization.
      type: object
      properties:
        error:
          type: string
        localizations:
          type: array
          items:
            $ref: '#/components/schemas/ProductLocalization'
        productId:
          type: string
        requestedLocales:
          type: array
          items:
            $ref: '#/components/schemas/Locale'
        returnedFailedLocales:
          type: array
          items:
            $ref: '#/components/schemas/Locale'
        returnedSuccessfulLocales:
          type: array
          items:
            $ref: '#/components/schemas/Locale'
        version:
          type: string
    Locale:
      description: Locale for localization.
      type: object
      properties:
        country:
          type: string
        language:
          type: string
    Localization:
      description: Localization data.
      type: object
      properties:
        longText:
          type: string
        name:
          type: string
        taggedTexts:
          $ref: '#/components/schemas/TaggedTexts'
        text:
          type: string
    ProblemDetail:
      type: object
      properties:
        detail:
          type: string
        instance:
          type: string
          format: uri
        properties:
          type: object
          additionalProperties:
            type: object
        status:
          type: integer
          format: int32
        title:
          type: string
        type:
          type: string
          format: uri
    ProductLocalization:
      description: Localization data of a Product.
      type: object
      properties:
        elements:
          type: array
          items:
            $ref: '#/components/schemas/ElementLocalization'
        globalCstics:
          type: array
          items:
            $ref: '#/components/schemas/CsticLocalization'
        locale:
          $ref: '#/components/schemas/Locale'
        longText:
          type: string
        messages:
          type: array
          items:
            $ref: '#/components/schemas/Localization'
        name:
          type: string
        taggedTexts:
          $ref: '#/components/schemas/TaggedTexts'
        text:
          type: string
    TaggedText:
      description: A text with free useable tags. See TextTag enum for known tags.
      type: object
      properties:
        tags:
          type: array
          items:
            type: string
        text:
          type: string
    TaggedTexts:
      type: object
      properties:
        texts:
          type: array
          items:
            $ref: '#/components/schemas/TaggedText'
  securitySchemes:
    basicScheme:
      scheme: basic
      type: http
tags:
  - description: Commmon service operations
    name: service
  - description: Write operations
    name: write
  - description: Read operations
    name: read

Generation Details

I'm using this maven plugin configuration ...

<plugin>
        <groupId>org.openapitools</groupId>
        <artifactId>openapi-generator-maven-plugin</artifactId>
        <version>7.17.0</version>
        <configuration>
          <generatorName>java</generatorName>
          <addCompileSourceRoot>true</addCompileSourceRoot>
          <generateModelTests>false</generateModelTests>
          <generateApiTests>false</generateApiTests>
          <skipOverwrite>false</skipOverwrite>
          <indentSize>2</indentSize>
          <lineLength>200</lineLength>
          <verbose>false</verbose>
          <skipIfSpecIsUnchanged>false</skipIfSpecIsUnchanged>
          <removeOperationIdPrefix>false</removeOperationIdPrefix>
          <operationIdNameMappings>original</operationIdNameMappings>
          <skipValidateSpec>false</skipValidateSpec>
          <strictSpec>true</strictSpec>
          <configOptions>
            <additionalModelTypeAnnotations><![CDATA[ @SuppressWarnings("all") @com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true) ]]></additionalModelTypeAnnotations>
            <enumUnknownDefaultCase>true</enumUnknownDefaultCase>
            <library>resttemplate</library>
            <java8>true</java8>
            <dateLibrary>java8</dateLibrary>
            <failOnUnknownProperties>false</failOnUnknownProperties>
            <sortModelPropertiesByRequiredFlag>false</sortModelPropertiesByRequiredFlag>
            <sortParamsByRequiredFlag>false</sortParamsByRequiredFlag>
            <skipSortingOperations>true</skipSortingOperations>
            <generateClientAsBean>false</generateClientAsBean>
            <useJakartaEe>true</useJakartaEe>
          </configOptions>
        </configuration>
...
Steps to reproduce

I will provide a unit test via org.openapitools.codegen.java.JavaClientCodegenTest.testApiComponentNamedLocale()

Suggest a fix

Not sure .. may this "Locale" is now a forbidden component name? If not the generator need a fix use everywhere the full qualified name of the custom component "Locale" type .. to avoid mixing with java.util.Locale type.

ahoehma avatar Nov 02 '25 12:11 ahoehma

what about using model name mapping option to workaround the issue?

https://github.com/OpenAPITools/openapi-generator/blob/master/docs/customization.md#name-mapping

wing328 avatar Nov 02 '25 13:11 wing328

@wing328 until yet I was not aware of this ... thanks I will read this asap.

Update: the bug here is not about properties ... the whole type is incorrect So I guess mapping is not an option

But beside that workaround I think its worth to check why this happens since > 7.15.0.

Maybe this is not the fault of the openapi-generator ... I will try to find out why this happens.

ahoehma avatar Nov 02 '25 15:11 ahoehma

Does anyone here can give me an hint where the "real" full qualified name resolution happens for the collected "imports". As far as I see all the "import" just simple names during "processing" the api-spec... But then in the final step where the java-files is generated via template the imports become "full". For example in my provided test ...

package org.openapitools.client.model;

import java.util.Objects;
import java.util.Arrays;
import java.util.Locale;  // <<<<<<<<<<<<<<<<< where is "java.util." added to "Locale"?
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.openapitools.client.model.Locale;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonTypeName;

@wing328 can u drop me some hints please? I have the feeling that the code where the java-imports are collected the difference between custom-types (from the openapi-spec) and (standard) java-types (and also other "standards" like jackson types) are not really clear. In the code there are simply strings and most of the time not full qualified. With some more insights I may can think about a better solution.

ahoehma avatar Nov 02 '25 17:11 ahoehma

Hmmm .. anyone else can support here maybe??

ahoehma avatar Nov 11 '25 20:11 ahoehma

Hi @ahoehma ! This is something I identified also, see #22313 .

Chrimle avatar Nov 11 '25 21:11 Chrimle

@ahoehma is this the failing assertion?

JavaFileAssert.assertThat(files.get("Locale.java")).isNormalClass()
          .hasNoImports("java.util.Locale");

Because if it is, then it is the added import in 7.15.0 - 7.16.0 that is causing this to fail.

Chrimle avatar Nov 11 '25 21:11 Chrimle

@ahoehma is this the failing assertion?

JavaFileAssert.assertThat(files.get("Locale.java")).isNormalClass() .hasNoImports("java.util.Locale"); Because if it is, then it is the added import in 7.15.0 - 7.16.0 that is causing this to fail.

Exactly. And to import java.lang.Locale would pretty okay if this is not conflicting with a "com.custom.Locale" class. My test is, with the new knowledge that java.lang.Locale is always imported, not correct anymore. I was trying to define a test which shows this conflict. I can try to adapt the test to show that "my own" Locale is used.

But wait a second .. do you tell me that "java.lang.Locale" is also imported INTO my "own" Locale class... then ... this make no sense in my case 💯

ahoehma avatar Nov 12 '25 05:11 ahoehma

@Chrimle ... I build your PR locally and used your version of open-api-generator and good news ... my compiler errors because of "Locale" are gone! Yippi! 👍

ahoehma avatar Dec 05 '25 08:12 ahoehma