. Saturday's was about how you iterate; yesterday's was about new platform APIs in the core; today's is about a run of pieces that change how you write the structural parts of an app.
What is Codename One? Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at .
In application code you call the generated Api class the same way you would call any other Java method:
PetApi pets = new PetApi();
// Returns AsyncResource<Pet>; resolves with the deserialised object.
pets.getPetById(42).onResult((pet, err) -> {
if (err == null) Log.p("Got " + pet.getName());
});
// Returns AsyncResource<List<Pet>>.
pets.findPetsByStatus("available").onResult((list, err) -> {
if (err == null) {
for (Pet p : list) Log.p(p.getName());
}
});
// POST with a request body. addPet takes a Pet, returns a Pet.
Pet candidate = new Pet();
candidate.setName("Mittens");
candidate.setStatus("available");
pets.addPet(candidate).onResult((created, err) -> { /* ... */ });
There is no hand-rolled ConnectionRequest setup, no manual JSON parsing, no string-typed request bodies. The generated client takes a typed Pet, serializes it with Mappers.toJson(...), fires the right HTTP verb, deserializes the response with Mappers.fromJson(...), and surfaces the result through the framework's AsyncResource so your callback fires on the EDT.
For teams who already publish an OpenAPI spec as part of their backend (most modern backend frameworks do this automatically; FastAPI, Spring's springdoc-openapi, NestJS, ASP.NET Core, Go's gnostic), the practical effect is that the mobile client's bindings stay in sync with the backend without anyone hand-writing a single network call. Update the spec, re-run mvn generate-sources, and the new and changed endpoints land in your app as typed Java the IDE picks up immediately.
It is the kind of change that is most useful when you do not know you have it: pull a fresh spec, rebuild, and your IDE highlights every place in the codebase that called a renamed endpoint or passed the wrong type to a parameter.
SQLite ORM
@Entity marks the class; @Id and @Column shape the schema; @DbTransient opts a field out:
@Entity
public class TodoItem {
@Id @Column long id;
@Column String title;
@Column(name = "completed_at")
Date completedAt;
@DbTransient Object cachedView;
}
Dao<TodoItem> dao = EntityManager.open("todos.db").dao(TodoItem.class);
dao.createTable();
dao.insert(new TodoItem(0, "Read the post", null));
List<TodoItem> open = dao.find("completed_at IS NULL", new Object[] {});
TodoItem byId = dao.findById(42);
dao.delete(byId);
The generated DAO does the typed work underneath. No reflection in insert; the generated code calls setString(1, e.title) and setLong(2, e.id) directly against the SQLite PreparedStatement. Validation at build time catches missing @Id, fields that look like relationships but are not yet supported, and abstract entity classes; the build fails with a class name and a reason.
For JPA / Hibernate developers, the API is intentionally familiar. @Entity, @Id, @Column, and @Transient (here renamed @DbTransient to avoid colliding with java.beans.Transient) carry the same meaning they do under javax.persistence / jakarta.persistence. The EntityManager name is the same. Dao#findById, Dao#findAll, Dao#find(where, params), Dao#insert, Dao#update, Dao#delete line up with the basic JPA repository contract. The query language is plain SQL (there is no JPQL or Criteria DSL) but the annotation surface, the lifecycle, and the runtime methods will feel like a long-lost friend to anyone with server-side Java persistence experience.
JSON / XML mapping
@Mapped marks a class as a transferable POJO. @JsonProperty and @XmlElement (plus @XmlRoot, @XmlAttribute, @JsonIgnore, @XmlTransient) shape the wire format. The runtime entry points are Mappers.toJson(...), Mappers.fromJson(...), Mappers.toXml(...), Mappers.fromXml(...):
@Mapped
public class User {
@JsonProperty("user_id") long id;
@JsonProperty String name;
@JsonProperty("created_at")
Date createdAt;
@JsonIgnore String passwordHash;
}
String json = Mappers.toJson(user);
User back = Mappers.fromJson(json, User.class);
The same @Mapped POJO is the type the typed Rest helpers accept:
Rest.get("https://api.example.com/users/42")
.fetchAsMapped(User.class)
.onResult((user, err) -> { /* ... */ });
Rest.get("https://api.example.com/users")
.fetchAsMappedList(User.class)
.onResult((users, err) -> { /* ... */ });
Rest.fetchAsJsonList (top-level JSON arrays, no {"root":[...]} envelope trick), JSONWriter (the complement of JSONParser, with fluent builders and streaming variants for Writer and OutputStream), and URLImage.setDefaultBearerToken (auth headers on image fetches) all ship alongside.
For JAXB developers, the XML surface (@XmlRoot, @XmlElement, @XmlAttribute, @XmlTransient) is a direct port of the long-established javax.xml.bind.annotation surface. The same model class can be both @XmlRoot-decorated and @JsonProperty-decorated, which gives you a single source of truth for both wire formats. The JSON surface adopts the Jackson convention (@JsonProperty, @JsonIgnore) that nearly every modern JVM JSON binding (Jackson, Moshi, kotlinx-serialization) inherited.
Component binding with validation
The fourth annotation processor on the same pipeline is the component binder. @Bindable marks a model class; @Bind(name = "userField") ties a field to a component on a form by the component's name. Field-level validation annotations compose with @Bind on the same field:
@Bindable
public class SignupModel {
@Bind(name = "userField") @Required @Length(min = 3)
private String user;
@Bind(name = "emailField") @Required @Email
private String email;
@Bind(name = "ageField") @Numeric(min = 13, max = 120)
private String age;
@Bind(name = "roleField") @ExistIn({ "admin", "editor", "viewer" })
private String role;
}
The matching form sets a name on each component so the binder can find them:
TextField user = new TextField(); user.setName("userField");
TextField email = new TextField(); email.setName("emailField");
TextField age = new TextField(); age.setName("ageField");
ComboBox<String> role = new ComboBox<>("admin", "editor", "viewer");
role.setName("roleField");
Button submit = new Button("Sign up");
Form form = new Form("Sign Up", BoxLayout.y());
form.add(user).add(email).add(age).add(role).add(submit);
form.show();
SignupModel model = new SignupModel();
Binding binding = Binders.bind(model, form);
binding.getValidator().addSubmitButtons(submit);
Binding is the handle: refresh() re-reads the model into the components, commit() writes the components back, disconnect() tears the listeners down. Multiple validation annotations on a single field compose via Validator.addConstraint(Component, Constraint...) and GroupConstraint (first failure wins). @Validate(MyClass.class) is the escape hatch for hand-written Constraint implementations. The validation set: @Required, @Length, @Regex, @Email, @Url, @Numeric, @ExistIn, @Validate.
The new BindAttr enum lets @Bind target a specific attribute of the component (TEXT, UIID, SELECTED, ...) when the default ("write a String field into the component's text") is not what you want.
SVG at build time
Drop an SVG into src/main/css/, alongside theme.css:
src/main/css/
theme.css
star.svg
gradient_circle.svg
path_arrow.svg
rounded_button.svg
wave.svg
pro_badge.svg
clipped_badge.svg
After the next build, every SVG is a regular Codename One Image. An SVG handled by the transcoder is a vector image, but it is still an Image. Everywhere a raster Image works (Label.setIcon, Button.setIcon, BorderLayout.NORTH, the toolbar, a MultiButton's leading icon, a CSS background: url(...) rule), the SVG works too. The difference is that it stays crisp at any size: the same source file is sharp at a 16-point list-row icon, a 64-point hero header, and a 256-point launch screen, on every DPI bucket.
A grid of the static SVGs from the hellocodenameone fixture, rendered through the new pipeline:
, and both are visible in the screenshot above (the "Codename One / build-time SVG" wordmark in the rounded button, the "PRO" badge text, and the clip-path-shaped rounded-corner badge underneath). <text> and <tspan> work with single-style fills and transforms; <clipPath> referenced via clip-path="url(#id)" works against rect, circle, and path clip shapes (nested clip refs are ignored).
What is still not supported: SVG filter primitives, <mask> (treated as a clip, so alpha masking falls back to opaque), <radialGradient> (falls back to the first-stop color), and CSS-in-SVG (style rules inside the SVG document; the transcoder reads presentation attributes and the inline style="..." attribute, but a <style> element with selectors is not parsed).
If you hit an SVG that does not transcode the way you expect, please open an issue at , this is a non-issue on most apps; if you have explicitly pinned ios.metal=false, expect some visual regressions on SVG content and let us know which.
The coverage matrix and troubleshooting are at
The Lottie transcoder lives in maven/lottie-transcoder/. It parses Bodymovin JSON with no external dependencies (the framework's built-in JSON parser carries the load) and lowers each file into the same SVGDocument model the SVG path uses. The same JavaCodeGenerator emits the same GeneratedSVGImage subclass, and the same SVGRegistry registers it under the source filename. No new Image base class, no new registry, no per-port wiring, since the SVG path's JavaSE reflective load and iOS / Android Stub weaving already cover the new format.
Coverage in v1: shape layers (rc / el / sh) with solid fills and strokes; layer transforms (anchor, position, scale, rotation, opacity); animated rotation, position, and scale collapsed to a two-keyframe loop; solid-color layers as filled rects. Most icon-grade Bodymovin exports lower cleanly. Complex character animations from After Effects with image references, masks, and effects do not, and the transcoder logs which layers it dropped so the source of any blank output is obvious.
Same ask as for SVG: if a Lottie / Bodymovin file does not transcode the way you expect, please open an issue at in the developer guide.
The JavaScript port bridges the router into window.history so navigating the in-app router pushes a real entry into the browser's session history. Back and forward in the browser drive the router; reloading the page lands at the deep-link URL; sharing the URL out of the address bar takes a colleague to the same in-app location.
How it works: the build-time codegen pipeline
Everything above sits on a single Maven-plugin pass.
The plugin has an AnnotationProcessor SPI and two new Mojos: cn1:generate-annotation-stubs (in generate-sources) and cn1:process-annotations (in process-classes). The orchestrator ASM-scans target/classes, dispatches to every registered processor, validates the annotated classes, and emits a typed runtime artifact next to each one plus a tiny Index class that registers everything with a public runtime registry. Adding a new processor later is a matter of dropping it into META-INF/services with no orchestrator changes.
The reason this runs against bytecode rather than against source text is that the source-regex prototype was scrapped early. The bytecode pass sees the JVM's view of the project (extends Form is a thing the JVM actually knows, not a pattern we have to hope the user wrote a specific way), rule violations come back with class names and reasons, and the build fails fast before any generated .class lands on disk. The infrastructure shares the ASM passes that the BytecodeComplianceMojo's existing String rewrites already use.
A small stub source is emitted under target/generated-sources/cn1-annotations/ during generate-sources so application code that references the generated registry resolves at compile time. The real .class overwrites the stub later in process-classes. Standard "compile against a stub, link against the real thing" pattern; it just works inside a single Maven build instead of needing a multi-module split.
cn1-core ships a no-op stub of each generated index (RoutesIndex, MappersIndex, BindersIndex, DaosIndex) so application code compiles even when the project has no annotated classes. The build-time processor shadows each stub with the real implementation before packaging.
The SVG and Lottie transcoders sit on a parallel pipeline (declarative graphics files in place of annotations), but they emit the same shape of code and obey the same constraints. The practical effect is that the kind of code that historically required reflection at runtime (with all the obfuscation hazards and surprise allocations that come with that) now happens once at build time and produces direct, dead-code-eliminable, rename-safe symbol references.
Wrapping up
That closes this release's post series. We already have some pretty big features lined up for this Friday's release post; the headline pieces are the most substantial things to land in months and worth checking back for.
Back to the weekly index.
SOCIAL SHARE CARD GENERATOR