using java records as DTOs
27. August 2023
When generating interfaces and schema classes (dtos) from an OpenAPI, wouldn’t it be nice to use java record
s instead of plain pojos?
This would result in less code, immutable fields and a couple of auto-generated methods: getters, setters, equals()
, hashCode()
and toString()
.
openapi-processor-spring/micronaut release 2023.3 is able to generate records instead of pojos from OpenAPI schemas.
This is possible because openapi-processor doesn’t generate inheritance hierarchies. This would be a problem with records because records do not support the extends
keyword.
openapi-processor does support allOf
by merging all item properties into a single object, and it can add a marker interface to oneOf
items to use a named type in the api interfaces instead of Object
.
Both ways work with records!
a little bit of OpenAPI
Here is a simple OpenAPI snippet that defines a schema:
components:
schemas:
Foo:
type: object
properties:
foo:
type: string
id:
type: string
format: uuid
OpenAPI as pojo
with its default settings openapi-processor will generate a pojo dto class with some getter/setter noise.
Foo.java
pojo dtopackage io.openapiprocessor.openapi.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.openapiprocessor.openapi.support.Generated;
import java.util.UUID;
@Generated(value = "openapi-processor-spring", version = "2023.3")
public class Foo {
@JsonProperty("foo")
private String foo;
@JsonProperty("id")
private UUID id;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
}
It is probably not a big issue because you won’t look at it very often. :-)
a small piece of configuration
It may still be useful to use java records because they are immutable and provide equals() plus hasCode() out of the box.
The generated pojos are not immutable, and they don’t offer equals()
or hashCode()
.
We could add it via annotation mapping and lombok. How this works is described in object annotation mapping & lombok.
But we could avoid lombok for this and simply use the built-in records.
The only thing we have to do to get records is to enable it in the options section of the mapping.yaml
:
openapi-processor-mapping: v4
options:
package-name: io.openapiprocessor.openapi
model-type: record (1)
map:
types:
- type: string:uuid => java.util.UUID
1 | that is the interesting line that switches generation of pojos to records. |
If model-type
is not given the processor will generate pojos.
This is a global setting, i.e. either all schemas are generated as pojo or all schemas are generated as record.
OpenAPI as records
After switching to records the generated Foo.java
will look like this:
Foo.java
record dtopackage io.openapiprocessor.openapi.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.openapiprocessor.openapi.support.Generated;
import java.util.UUID;
@Generated(value = "openapi-processor-spring", version = "2023.3")
public record Foo(
@JsonProperty("foo")
String foo,
@JsonProperty("id")
UUID id
) {}
less code, the same properties, the same annotations, no explicit getter/setters.
Now we have to find out how well this works in real code.. :-)
summary
This article shows how openapi-processor can generate java records instead of pojos.
To learn more about openapi-processor and how to generate controller interfaces and dto classes from an OpenAPI description take a look at the documentation.