Skip to main content
ElastiCORE 3.0 is a release candidate: 3.0.261022-rc1 on Maven Central. Projects on the 2.2 line: see the 2.2 documentation.
Version: 3.0

Fields and types

A field is one line: name: type[(size)][!] [@annotations] -- label.

Value types​

Every value type a field can have, with the Java type, the JSON shape, the TypeScript type and the Apache Ossie DataType it is exported as.

DSL typeJavaJSONTSOssieNotes
string / string(n)String"text"stringStringn = maximum length, ! = NOT NULL
text / longtextString"text"stringStringcolumnDefinition = "TEXT" / "LONGTEXT"
clobString"text"stringString@Lob
jsonString"{...}" (a string)stringOpaquea JSON column
int / IntegerInteger42numberInteger
long / LongLong42numberInteger
bigintegerBigInteger123456789012345678901numberInteger
quantityQuantity5numberInteger3.0 value type: a count that is never negative
decimal(p.s) / bigdecimal(p.s)BigDecimal12.34numberDecimalprecision, scale
money / money(p.s)Money99.95numberDecimal3.0 value type: an amount, default (19,2)
float / doubleFloat / Double1.5numberFloatapproximate - not for money
booleanBooleantruebooleanBoolean
dateLocalDate"2026-10-03"stringDate
timeLocalTime"09:30:00"stringTime
datetimeLocalDateTime"2026-10-03 09:30:00"stringDateTimeno time zone; not ISO T
timestampjava.time.Instant"2026-10-03T00:30:00Z"stringDateTimeTzan instant; answered in UTC
blobbyte[]"AQID/w==" (base64)stringOpaque@Lob
EnumNamethe enumits code ("PLC")the enumStringthe @db(code) value is stored
EntityNamea reference(its id field)-relationship@ManyToOne by default
  • Which time type: what people read (a business day, an appointment) is date / datetime; "when did it happen" (a payment approved, an event, an audit) is timestamp.
  • Not types: uuid (use string(36), or string @id @genid for a UUID key), short / byte (int), char (string(1)), OffsetDateTime / ZonedDateTime (timestamp).
  • Collections: List<T>, Set<T>, Map<K,V>, arrays int[], string[].
  • The table is the 3.0 rule engine's; every type in it is generated, saved and sent over HTTP by TypeRoundTripTest in elasticore-template-ddd.

money and quantity​

Amounts and counts as value objects (io.elasticore.springboot3.domain). The column and the JSON stay decimal and integer; the code in between uses Money and Quantity.

SalesOrderLine:
fields:
qty: quantity! @min(1)
unitPrice: money(12.2)! @min(0.01, 'a unit price must be positive')
amount: money(14.2)
line.setAmount(line.getUnitPrice().times(line.getQty()));
total = total.plus(line.getAmount());
stock.setOnHand(stock.getOnHand().minus(qty)); // not enough: 422

Money: plus, minus, times(Quantity / long / BigDecimal), rounded(n) (half-up), isNegative; 10 and 10.00 are the same amount. Quantity is never negative (minus below zero answers 422 not enough). @default(0) becomes Money.of("0") / Quantity.of(0). A signed change (stock in +, out -) is not a quantity: use int.

Keys​

WriteMeans
id: long @id @sequencenumbered by the database; leave it out when creating
id: string @id @genida UUID (UUID.randomUUID())
code: string(15) @id @genidIdUtils.newId(15): a 3-letter prefix and 12 random letters and digits
id: string @id @genid(com.acme.Ids.next)a static method of yours
isbn: string(13)! @ida key the client sends; a create with an existing key answers 409

Validation​

WriteMeans
string(100)!required, at most 100 characters - "" still passes
string(100)! @notblankrequired and not empty or blank
string(20)! @length(min=4)4 to 20 characters
string(100) @emailan e-mail address
int @min(1) @max(10)a whole number from 1 to 10 (on an integer field whole numbers only; decimal and money fields get @DecimalMin/@DecimalMax)
money(10.2)! @min(0.01, 'price must be positive')a minimum with its message - @DecimalMin
date @past, date @futurebefore / after today (@pastorpresent, @futureorpresent include today)
string(13)! @pattern('^[0-9]{13}$', 'ISBN must be 13 digits')the whole value matches the regex; 400 with the message
lines: List<OrderLine> ... @minsize(1, 'an order needs a line')at least n members; @maxsize(n, '...') at most

@pattern: also @pattern(regexp="...", message="..."). Without a message both server and client say "<label> is not in the expected form". Write a backslash doubled (\\d) or use a class such as [0-9] - a single \d is a DSL warning. It goes into the DTO and the TypeScript client (not the entity, not the search DTO); on a date field @pattern(yyyyMMdd) is still a date format.

@minsize(n) with n of 1 or more also adds @NotNull: an empty list and a missing list both answer 400; the TypeScript validate...() asks the same. In the DDD bundles the root checks it at save time too (422).

Values the server sets​

WriteMeans
joinedAt: date @readonlyin answers, never taken from a request, not required - your code fills it (beforeSave)
grade: string(10)! @noinputthe same (@noinput is an alias); the column stays NOT NULL, so the server must fill it
orderedAt: datetime @readonly @default(now)the time the row is made - request values ignored, kept on every update
joinedOn: date @readonly @default(now)today
createdAt: datetime @updatable(false) @jpa:org.springframework.data.annotation.CreatedDatethe audit pair - needs @EnableJpaAuditing
modifiedAt: datetime @jpa:org.springframework.data.annotation.LastModifiedDatechanges on every save (audit)

On the v3 bundles @readonly:

  • drops the DTO's @NotNull / @NotBlank (a request cannot send the value) and adds @Schema(accessMode = READ_ONLY);
  • is not written by the mapper from a DTO to the entity. Code that put a value in a DTO and saved it (dto.setGrade("A"), then service.save(dto)) no longer stores it - set it on the entity (beforeSave, a method of the root);
  • on a field of an entity applies to all its DTOs; on a field of a dto.yml DTO, to that DTO only.

@default(now) is LocalDateTime.now() for datetime, and now for date, time and timestamp (Instant.now()) - an initial value of the entity, never a default of the DTO. There is no @createtime.

Other field annotations​

WriteMeans
@default(value)a default: @default("ACTIVE"), @default(true), @default(UserRole.USER)
@updatable(false)@Column(updatable = false); on a reference in the DDD bundles the id field inherits it - an update keeps the stored one
@writeonlyaccepted in requests, never in answers (a card number)
@passwordstored as a hash only - see Access and sign-in
@access(admin)only an administrator writes it - see Access and sign-in
@search(...)a search condition - see Search
@formula("sql")a column the database computes (Hibernate)
@Column(name="col", length=n) / @column(name)the column mapping
@jpa:<annotation>a JPA or Spring Data annotation written as is
@env:namean annotation defined in env.yml (config.annotations)
@terms(price, 가격)synonyms for the Ossie export