Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ public void processOpts() {
additionalProperties.put("jsr310", "true");
typeMapping.put("date", "LocalDate");
importMapping.put("LocalDate", "java.time.LocalDate");
typeMapping.put("time-local","LocalTime");
importMapping.put("LocalTime", "java.time.LocalTime");
if ("java8-localdatetime".equals(dateLibrary)) {
typeMapping.put("DateTime", "LocalDateTime");
Expand Down Expand Up @@ -1411,6 +1412,13 @@ public String toDefaultValue(CodegenProperty cp, Schema schema) {
return "URI.create(\"" + escapeText(String.valueOf(schema.getDefault())) + "\")";
}
return null;
} else if (ModelUtils.isTimeLocalSchema(schema)) {
if (schema.getDefault() != null) {
if ("java8".equals(getDateLibrary())) {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: time-local defaults are skipped for java8-localdatetime because default generation checks "java8".equals(getDateLibrary()) while type mapping uses dateLibrary.startsWith("java8").

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java, line 1417:

<comment>`time-local` defaults are skipped for `java8-localdatetime` because default generation checks `"java8".equals(getDateLibrary())` while type mapping uses `dateLibrary.startsWith("java8")`.</comment>

<file context>
@@ -1411,6 +1412,13 @@ public String toDefaultValue(CodegenProperty cp, Schema schema) {
             return null;
+        } else if (ModelUtils.isTimeLocalSchema(schema)) {
+            if (schema.getDefault() != null) {
+                if ("java8".equals(getDateLibrary())) {
+                    return String.format(Locale.ROOT, "LocalTime.parse(\"%s\")", schema.getDefault());
+                }
</file context>
Fix with Cubic

return String.format(Locale.ROOT, "LocalTime.parse(\"%s\")", schema.getDefault());
}
}
return null;
} else if (ModelUtils.isStringSchema(schema)) {
if (schema.getDefault() != null) {
String _default;
Expand Down Expand Up @@ -1493,6 +1501,10 @@ public String toDefaultValue(CodegenProperty cp, Schema schema) {
value.asText(),
"java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME.withZone(java.time.ZoneId.systemDefault())");
}
} else if(ModelUtils.isTimeLocalSchema(propertySchema)) {
if("java8".equals(getDateLibrary())) {
defaultPropertyExpression = String.format(Locale.ROOT, "java.time.LocalTime.parse(\"%s\")", value.asText());
}
} else if(ModelUtils.isUUIDSchema(propertySchema)) {
defaultPropertyExpression = "java.util.UUID.fromString(\"" + value.asText() + "\")";
} else if(ModelUtils.isStringSchema(propertySchema)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,12 @@ public static boolean isDateTimeSchema(Schema schema) {
&& SchemaTypeUtil.DATE_TIME_FORMAT.equals(schema.getFormat()));
}

public static boolean isTimeLocalSchema(Schema schema) {
// format: time-local, see https://spec.openapis.org/registry/format/time-local.html
return (SchemaTypeUtil.STRING_TYPE.equals(getType(schema))
&& "time-local".equals(schema.getFormat()));
}

public static boolean isPasswordSchema(Schema schema) {
return (schema instanceof PasswordSchema) ||
// double
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,7 @@

import java.io.File;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.*;
import java.util.*;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -531,6 +528,15 @@ public void toDefaultValueDateTimeLegacyTest() {

// dateLibrary <> java8
Assert.assertEquals(defaultValue, "1984-12-19T03:39:57-09:00");

// Test default value for time-local format
StringSchema timeLocalSchema = new StringSchema();
timeLocalSchema.setFormat("time-local");
timeLocalSchema.setDefault(LocalTime.parse("10:15:30"));
defaultValue = codegen.toDefaultValue(timeLocalSchema);

// dateLibrary <> java8
Assert.assertEquals(defaultValue, "10:15:30");
}

@Test
Expand Down Expand Up @@ -593,6 +599,13 @@ public void toDefaultValueTest() {
numberSchema.setFormat("double");
defaultValue = codegen.toDefaultValue(codegen.fromProperty("", schema), numberSchema);
Assert.assertEquals(defaultValue, doubleValue + "d");

// Test default value for time-local format
StringSchema timeLocalSchema = new StringSchema();
timeLocalSchema.setFormat("time-local");
timeLocalSchema.setDefault("10:15:30");
defaultValue = codegen.toDefaultValue(codegen.fromProperty("", timeLocalSchema), timeLocalSchema);
Assert.assertEquals(defaultValue, "LocalTime.parse(\"10:15:30\")");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,35 @@ public void generateFormatForDateAndDateTimeQueryParam() throws IOException {
.containsWithNameAndAttributes("DateTimeFormat", ImmutableMap.of("iso", "DateTimeFormat.ISO.DATE_TIME"));
}

@Test
public void generateLocalTimeForTimeLocalFormat() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');

OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/spring/date-time-parameter-types-for-testing.yml", null, new ParseOptions()).getOpenAPI();

SpringCodegen codegen = new SpringCodegen();
codegen.setOutputDir(output.getAbsolutePath());

ClientOptInput input = new ClientOptInput();
input.openAPI(openAPI);
input.config(codegen);

DefaultGenerator generator = new DefaultGenerator();
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "false");
generator.setGenerateMetadata(false);
generator.opts(input).generate();

JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/model/Pet.java"))
.hasImports("java.time.LocalTime")
.assertProperty("feedingTime").withType("LocalTime");
}

@Test
public void interfaceDefaultImplDisableWithResponseWrapper() {
final SpringCodegen codegen = new SpringCodegen();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,7 @@ components:
type: string
format: date
default: '2021-01-01'
feedingTime:
type: string
format: time-local
default: '10:15:30'
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.lang.Nullable;
Expand Down Expand Up @@ -40,6 +41,8 @@ public class Pet {
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
private LocalDate dateOfBirth = LocalDate.parse("2021-01-01");

private LocalTime feedingTime = LocalTime.parse("10:15:30");
Comment thread
Allsimon marked this conversation as resolved.

public Pet() {
super();
}
Expand Down Expand Up @@ -177,6 +180,27 @@ public void setDateOfBirth(LocalDate dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}

public Pet feedingTime(LocalTime feedingTime) {
this.feedingTime = feedingTime;
return this;
}

/**
* Get feedingTime
* @return feedingTime
*/
@Valid
@Schema(name = "feedingTime", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@JsonProperty("feedingTime")
public LocalTime getFeedingTime() {
return feedingTime;
}

@JsonProperty("feedingTime")
public void setFeedingTime(LocalTime feedingTime) {
this.feedingTime = feedingTime;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -191,12 +215,13 @@ public boolean equals(Object o) {
Objects.equals(this.happy, pet.happy) &&
Objects.equals(this.price, pet.price) &&
Objects.equals(this.lastFeed, pet.lastFeed) &&
Objects.equals(this.dateOfBirth, pet.dateOfBirth);
Objects.equals(this.dateOfBirth, pet.dateOfBirth) &&
Objects.equals(this.feedingTime, pet.feedingTime);
}

@Override
public int hashCode() {
return Objects.hash(atType, age, happy, price, lastFeed, dateOfBirth);
return Objects.hash(atType, age, happy, price, lastFeed, dateOfBirth, feedingTime);
}

@Override
Expand All @@ -209,6 +234,7 @@ public String toString() {
sb.append(" price: ").append(toIndentedString(price)).append("\n");
sb.append(" lastFeed: ").append(toIndentedString(lastFeed)).append("\n");
sb.append(" dateOfBirth: ").append(toIndentedString(dateOfBirth)).append("\n");
sb.append(" feedingTime: ").append(toIndentedString(feedingTime)).append("\n");
sb.append("}");
return sb.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.lang.Nullable;
Expand Down Expand Up @@ -40,6 +41,8 @@ public class Pet {
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
private LocalDate dateOfBirth = LocalDate.parse("2021-01-01");

private LocalTime feedingTime = LocalTime.parse("10:15:30");
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: New LocalTime model field is generated without Spring @DateTimeFormat, unlike other temporal fields, creating a Spring formatting/binding parity gap for time-local.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/openapi3/client/petstore/spring-cloud-date-time/src/main/java/org/openapitools/model/Pet.java, line 44:

<comment>New `LocalTime` model field is generated without Spring `@DateTimeFormat`, unlike other temporal fields, creating a Spring formatting/binding parity gap for `time-local`.</comment>

<file context>
@@ -40,6 +41,8 @@ public class Pet {
   @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
   private LocalDate dateOfBirth = LocalDate.parse("2021-01-01");
 
+  private LocalTime feedingTime = LocalTime.parse("10:15:30");
+
   public Pet() {
</file context>
Suggested change
private LocalTime feedingTime = LocalTime.parse("10:15:30");
@DateTimeFormat(iso = DateTimeFormat.ISO.TIME)
private LocalTime feedingTime = LocalTime.parse("10:15:30");
Fix with Cubic

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DateTimeFormat is not needed since Spring Boot 3

Do I need to add it ? I'm not sure any Spring Boot 2 users will want to use a recent generator anyway... see #23269

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also open to create another PR that drop this annotation


public Pet() {
super();
}
Expand Down Expand Up @@ -177,6 +180,27 @@ public void setDateOfBirth(LocalDate dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}

public Pet feedingTime(LocalTime feedingTime) {
this.feedingTime = feedingTime;
return this;
}

/**
* Get feedingTime
* @return feedingTime
*/
@Valid
@Schema(name = "feedingTime", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@JsonProperty("feedingTime")
public LocalTime getFeedingTime() {
return feedingTime;
}

@JsonProperty("feedingTime")
public void setFeedingTime(LocalTime feedingTime) {
this.feedingTime = feedingTime;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -191,12 +215,13 @@ public boolean equals(Object o) {
Objects.equals(this.happy, pet.happy) &&
Objects.equals(this.price, pet.price) &&
Objects.equals(this.lastFeed, pet.lastFeed) &&
Objects.equals(this.dateOfBirth, pet.dateOfBirth);
Objects.equals(this.dateOfBirth, pet.dateOfBirth) &&
Objects.equals(this.feedingTime, pet.feedingTime);
}

@Override
public int hashCode() {
return Objects.hash(atType, age, happy, price, lastFeed, dateOfBirth);
return Objects.hash(atType, age, happy, price, lastFeed, dateOfBirth, feedingTime);
}

@Override
Expand All @@ -209,6 +234,7 @@ public String toString() {
sb.append(" price: ").append(toIndentedString(price)).append("\n");
sb.append(" lastFeed: ").append(toIndentedString(lastFeed)).append("\n");
sb.append(" dateOfBirth: ").append(toIndentedString(dateOfBirth)).append("\n");
sb.append(" feedingTime: ").append(toIndentedString(feedingTime)).append("\n");
sb.append("}");
return sb.toString();
}
Expand Down