<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shai Almog</title>
    <description>The latest articles on DEV Community by Shai Almog (@codenameone).</description>
    <link>https://dev.to/codenameone</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F417973%2Fa7b22dd9-5565-48f5-bfab-7e5035b3888f.png</url>
      <title>DEV Community: Shai Almog</title>
      <link>https://dev.to/codenameone</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codenameone"/>
    <language>en</language>
    <item>
      <title>Dash: An Endless Runner That Races Through Your City</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Sat, 14 Nov 2026 15:29:35 +0000</pubDate>
      <link>https://dev.to/codenameone/dash-an-endless-runner-that-races-through-your-city-57o7</link>
      <guid>https://dev.to/codenameone/dash-an-endless-runner-that-races-through-your-city-57o7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi9ebvugu2k85jqqls9kt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi9ebvugu2k85jqqls9kt.jpg" alt="Dash: a route picked on a city map and the same route as a 3D run" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.codenameone.com/blog/build-a-multiplayer-strategy-game-in-java/" rel="noopener noreferrer"&gt;Skirmish&lt;/a&gt; showed that most of a mobile game is app: the sign-in, the menus, the shop, the friends list, the pushes and the server. Dash is the second game template, and it is a different kind of game: an endless runner. A runner dashes down a track of lanes, jumps hurdles, slides under barriers and changes lanes around traffic. You can generate it from the &lt;a href="https://www.codenameone.com/initializr/" rel="noopener noreferrer"&gt;Initializr&lt;/a&gt; today, under your own name and package, and the source is MIT-0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; 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 &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;a href="https://www.codenameone.com/blog/release-2026-11-06/dash-promo-tour.mp4" rel="noopener noreferrer"&gt;Download the Dash tour&lt;/a&gt;.

&lt;h2&gt;
  
  
  A level that turns into 3D
&lt;/h2&gt;

&lt;p&gt;The trick I like most in Dash is that a level can start as a side-scroller and become a 3D runner halfway through. The first stretch of &lt;em&gt;Neon Causeway&lt;/em&gt; looks like a flat platformer: one row of hurdles and barriers, seen from the side. At 300 meters the camera swings behind the runner, the lanes open up, and the rest is a chase-camera runner.&lt;/p&gt;

&lt;p&gt;There is no second game behind that. The simulation always has lanes, heights and distances, and a hurdle is cleared the same way from either angle. A level only carries &lt;em&gt;camera cues&lt;/em&gt;, and the renderer blends between the two framings when the runner reaches one. Because the rules never see the camera, a level can change its view anywhere without changing who wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run your own street
&lt;/h2&gt;

&lt;p&gt;This is my favorite part to demo. Tap &lt;em&gt;Run your city&lt;/em&gt;, pick a start and a finish on a real street map, and the route becomes a level. Every street is a stretch of track, every turn is a corner on the side it turns to, and the next street's name is called out before you reach it. Landmarks along the way, a station or a museum, show up as boards beside the track.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdash-city-route.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="1732" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdash-city-route.png" alt="A route picked on the street map, with the landmarks along it pinned"&gt;&lt;/a&gt;Pick two points.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdash-city-run.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="1732" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdash-city-run.png" alt="The run down Cannon Street, with the street name under the score and the route map"&gt;&lt;/a&gt;Run the streets.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdash-city-landmark.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="1732" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdash-city-landmark.png" alt="A landmark's board beside the track as the runner passes it"&gt;&lt;/a&gt;Pass the landmarks.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;The map is Codename One's &lt;code&gt;MapView&lt;/code&gt;, which draws OpenStreetMap data itself on every platform, and the route comes from &lt;code&gt;Routing&lt;/code&gt;. The app reduces the route to streets and turns and hands it to the rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;CityRoute&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CityRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Old Harbour"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;420&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;CityRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;STRAIGHT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Quay Street"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;310&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;CityRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LEFT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Rope Walk"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;LevelDef&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;level()&lt;/code&gt; is deterministic, so the server doesn't need a map. It rebuilds the same level from the encoded route and checks your run against it. The level's id is a hash of the route, so every route gets its own leaderboard, and nobody can trim a hard route into an easy one and keep the same board.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leaderboards you can trust
&lt;/h2&gt;

&lt;p&gt;A run is never reported as a number. The app sends the seed and the input log, a few hundred bytes, and the server plays it through the same Java rules module the app runs. A typed-in score, or a log edited to dodge an obstacle the player hit, does not reproduce. The rules use no floating point and no clock, so the phone, the server and the replay viewer agree to the bit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBQIGFzIFBob25lCiAgICBwYXJ0aWNpcGFudCBTIGFzIFNlcnZlcgogICAgUC0-PlA6IFJ1biBvbiBzZWVkICsgcnVsZXMgbW9kdWxlCiAgICBQLT4-UzogU2VlZCArIGlucHV0IGxvZyAoYSBmZXcgaHVuZHJlZCBieXRlcykKICAgIFMtPj5TOiBSZXBsYXkgdGhlIGxvZyBvbiB0aGUgc2FtZSBydWxlcwogICAgYWx0IFJlcGxheSBtYXRjaGVzCiAgICAgICAgUy0-PlM6IFBvc3Qgc2NvcmUgdG8gYWxsLXRpbWUsIHdlZWtseSwgZnJpZW5kcyBhbmQgY3JldyBib2FyZHMKICAgICAgICBTLS0-PlA6IFB1c2ggdG8gdGhlIGZyaWVuZCB5b3UganVzdCBiZWF0CiAgICBlbHNlIEVkaXRlZCBsb2cgb3IgdHlwZWQtaW4gc2NvcmUKICAgICAgICBTLS0-PlA6IFJlamVjdGVkLCBpdCBkb2Vzbid0IHJlcHJvZHVjZQogICAgZW5k%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBQIGFzIFBob25lCiAgICBwYXJ0aWNpcGFudCBTIGFzIFNlcnZlcgogICAgUC0-PlA6IFJ1biBvbiBzZWVkICsgcnVsZXMgbW9kdWxlCiAgICBQLT4-UzogU2VlZCArIGlucHV0IGxvZyAoYSBmZXcgaHVuZHJlZCBieXRlcykKICAgIFMtPj5TOiBSZXBsYXkgdGhlIGxvZyBvbiB0aGUgc2FtZSBydWxlcwogICAgYWx0IFJlcGxheSBtYXRjaGVzCiAgICAgICAgUy0-PlM6IFBvc3Qgc2NvcmUgdG8gYWxsLXRpbWUsIHdlZWtseSwgZnJpZW5kcyBhbmQgY3JldyBib2FyZHMKICAgICAgICBTLS0-PlA6IFB1c2ggdG8gdGhlIGZyaWVuZCB5b3UganVzdCBiZWF0CiAgICBlbHNlIEVkaXRlZCBsb2cgb3IgdHlwZWQtaW4gc2NvcmUKICAgICAgICBTLS0-PlA6IFJlamVjdGVkLCBpdCBkb2Vzbid0IHJlcHJvZHVjZQogICAgZW5k%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="694" height="681"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every level has all-time, weekly, daily, friends and crew boards. Beat a friend's best and they get a push that opens that level's board.&lt;/p&gt;

&lt;h2&gt;
  
  
  Racing your friends
&lt;/h2&gt;

&lt;p&gt;Open a party, share the link, and everyone runs the same track at the same moment. The server runs the race in lockstep: each phone sends its inputs, the room broadcasts each tick's inputs to every seat, and its own copy of the race decides the places. A client whose state drifts from the server's is caught by a hash it sends every second.&lt;/p&gt;

&lt;p&gt;While you race you can talk. Voice uses Codename One's WebRTC API, peer to peer between each pair of players, with the game's own socket carrying the signalling. The server never touches the audio.&lt;/p&gt;

&lt;a href="https://www.codenameone.com/blog/release-2026-11-06/dash-gameplay-clip.mp4" rel="noopener noreferrer"&gt;Download the gameplay clip&lt;/a&gt;.

&lt;h2&gt;
  
  
  Build your own levels
&lt;/h2&gt;

&lt;p&gt;The track editor is in the app, on every platform. A level is a strip you paint on: tap a lane to drop a hurdle or a coin, tap the top row for a barrier across every lane, and drop camera cues where the view should change. &lt;em&gt;Playtest&lt;/em&gt; runs the draft on the real rules. Publishing requires the bot to finish the level, so nobody can publish one that cannot be beaten. Published packs can be rated, raced, remixed and moderated from the admin console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get it
&lt;/h2&gt;

&lt;p&gt;Choose &lt;strong&gt;Dash&lt;/strong&gt; in the &lt;a href="https://www.codenameone.com/initializr/" rel="noopener noreferrer"&gt;Initializr&lt;/a&gt;, or read the &lt;a href="https://www.codenameone.com/developer-guide/dash-implementors-guide/" rel="noopener noreferrer"&gt;guide chapter&lt;/a&gt; first. It walks through the rules, the camera, the referee, races, voice and the editor, and what to change to make the game yours.&lt;/p&gt;

&lt;p&gt;If you've ever wanted to ship a game but stalled on everything around the game, this is the part I'd copy. Sign-in, friends, pushes, a shop, moderation and a server that replays every run are already done, so the week you spend is on the fun part.&lt;/p&gt;

&lt;p&gt;Tomorrow: &lt;a href="https://www.codenameone.com/blog/launch-your-own-gym-app/" rel="noopener noreferrer"&gt;Pulse: Heart Rate Zones From the Watch to the Trainer's Screen&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Embedded Java: 20x Smaller Than Chrome, Faster Than Rust, Royalty Free</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Fri, 13 Nov 2026 14:35:42 +0000</pubDate>
      <link>https://dev.to/codenameone/embedded-java-20x-smaller-than-chrome-faster-than-rust-royalty-free-59p8</link>
      <guid>https://dev.to/codenameone/embedded-java-20x-smaller-than-chrome-faster-than-rust-royalty-free-59p8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F10709h6sl6xp0y318fpk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F10709h6sl6xp0y318fpk.jpg" alt="Embedded Java: 20x smaller than Chrome, faster than Rust" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Embedded software has the hardest job description in our industry. It has to run for years without a reboot. It has to fit in whatever RAM and flash the bill of materials allowed, because every chip you add is paid for again on every unit you ship. It has to respond instantly on a CPU slower than a phone from five years ago. And now it also has to look like the phone app, talk to the phone app, and report to a cloud service, because that's what customers expect from anything with a power cord.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; 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 &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Most teams solve that with three or four stacks. C or C++ on the device for size and control, a browser or Qt for the screen, Swift and Kotlin for the companion app, something else on the server. Every boundary between them is a protocol written twice and a bug waiting to happen.&lt;/p&gt;

&lt;p&gt;Codename One now runs on embedded Linux, and it covers every one of those pieces with one Java or Kotlin codebase. On our benchmark, the UI drew an animated gauge in &lt;strong&gt;18 MB of memory with no GPU&lt;/strong&gt;, about a twentieth of what a Chromium kiosk needed to draw the same thing. The headless backend served &lt;strong&gt;379,900 requests per second on two cores, 1.3 times Rust's &lt;code&gt;axum&lt;/code&gt;&lt;/strong&gt;. And we don't charge royalties.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming This Week
&lt;/h2&gt;

&lt;p&gt;Today is the embedded story. Then one post a day:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Saturday, November 7:&lt;/strong&gt; &lt;a href="https://www.codenameone.com/blog/build-an-endless-runner-in-java/" rel="noopener noreferrer"&gt;Dash: An Endless Runner That Races Through Your City&lt;/a&gt;. A game template that turns from 2D into 3D mid-run, builds levels from real streets, and lets friends race with voice chat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sunday, November 8:&lt;/strong&gt; &lt;a href="https://www.codenameone.com/blog/launch-your-own-gym-app/" rel="noopener noreferrer"&gt;Pulse: Heart Rate Zones From the Watch to the Trainer's Screen&lt;/a&gt;. A gym and studio template built around HealthKit, Health Connect and Bluetooth heart rate straps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monday, November 9:&lt;/strong&gt; &lt;a href="https://www.codenameone.com/blog/real-time-java-control-loops/" rel="noopener noreferrer"&gt;Real-Time Java: Control Loops the Garbage Collector Never Stops&lt;/a&gt;. What real time means, why it's hard for a managed language, and how we do it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tuesday, November 10:&lt;/strong&gt; &lt;a href="https://www.codenameone.com/blog/codename-one-on-xbox/" rel="noopener noreferrer"&gt;Packaging Java Apps for Xbox Developer Mode, and Why Consoles Need Help&lt;/a&gt;. One build hint today. Shipping to consoles needs help we can't get alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wednesday, November 11:&lt;/strong&gt; &lt;a href="https://www.codenameone.com/blog/cn1libs-with-a-server-side/" rel="noopener noreferrer"&gt;Port Your Spring Code With Fewer Edits, and Libraries That Bring Their Own Server&lt;/a&gt;. Logging, typed JSON, CORS, content negotiation and more of the JDK in the backend, plus cn1libs with a server half.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thursday, November 12:&lt;/strong&gt; &lt;a href="https://www.codenameone.com/blog/debug-dart-in-a-java-debugger/" rel="noopener noreferrer"&gt;Debug Your Dart in a Java Debugger, and JFX Central on a Phone&lt;/a&gt;. Dart call stacks, Flutter fixes from a real port, a faster web port and a real JavaFX app on an iPhone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/blog/build-an-endless-runner-in-java/" rel="noopener noreferrer"&gt;&lt;img width="420" height="908" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-11-06%2Fdash-promo-tour-poster.jpg" alt="Dash running in 3D on a neon track"&gt;&lt;/a&gt;Dash, mid-run in 3D.&lt;br&gt;
&lt;a href="https://www.codenameone.com/blog/launch-your-own-gym-app/" rel="noopener noreferrer"&gt;&lt;img width="420" height="746" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-11-06%2Fpulse-phone-trainer-poster.jpg" alt="Pulse showing live heart rate zones for a class"&gt;&lt;/a&gt;Pulse's live class view.&lt;br&gt;
&lt;a href="https://www.codenameone.com/blog/debug-dart-in-a-java-debugger/" rel="noopener noreferrer"&gt;&lt;img width="420" height="914" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-11-06%2FJFXCentral-demo-poster.jpg" alt="JFX Central running on an iPhone"&gt;&lt;/a&gt;JFX Central on an iPhone.&lt;p&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Two flavors, two kinds of product
&lt;/h2&gt;

&lt;p&gt;The port comes in two flavors. The easiest way to explain them is with two products.&lt;/p&gt;

&lt;h3&gt;
  
  
  A point of sale terminal: the UI flavor
&lt;/h3&gt;

&lt;p&gt;A POS terminal lives or dies on price. It needs a responsive touch screen, a receipt printer on a serial port, a card reader, and it has to sync with the store's back office. The merchant buys a hundred of them, so ten dollars of hardware is a thousand dollars on the order and the difference between winning and losing the deal.&lt;/p&gt;

&lt;p&gt;A GPU is one of the first things you'd like to cut. Android won't let you: AOSP needs a board support package with a GPU driver. Flutter can limp along without one, if Mesa emulates OpenGL on the CPU, and on our gauge that took 1.4 CPU cores and 112 MB. A Chromium kiosk runs in 400 MB of RAM. Codename One draws in software by default, through DRM/KMS or the Linux frame buffer, with no X11 or Wayland. It needs nothing from the board but a frame buffer, so the cheapest SoC that drives your screen is enough.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBzdWJncmFwaCBUW1BPUyB0ZXJtaW5hbCwgbm8gR1BVXQogICAgICAgIFVbQ29kZW5hbWUgT25lIFVJPGJyLz5zb2Z0d2FyZSByZW5kZXJlcl0gLS0-IEZbRnJhbWUgYnVmZmVyXQogICAgICAgIFUgLS0-IFBbUmVjZWlwdCBwcmludGVyPGJyLz5zZXJpYWwgcG9ydF0KICAgICAgICBVIC0tPiBSW0NhcmQgcmVhZGVyPGJyLz5ORkMgLyBVU0JdCiAgICBlbmQKICAgIFUgLS0-fHNoYXJlZCBEVE9zLCBSRVNUfCBDW0JhY2sgb2ZmaWNlIGJhY2tlbmRdCiAgICBNW01hbmFnZXIncyBwaG9uZSBhcHBdIC0tPnxzYW1lIERUT3N8IEM%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBzdWJncmFwaCBUW1BPUyB0ZXJtaW5hbCwgbm8gR1BVXQogICAgICAgIFVbQ29kZW5hbWUgT25lIFVJPGJyLz5zb2Z0d2FyZSByZW5kZXJlcl0gLS0-IEZbRnJhbWUgYnVmZmVyXQogICAgICAgIFUgLS0-IFBbUmVjZWlwdCBwcmludGVyPGJyLz5zZXJpYWwgcG9ydF0KICAgICAgICBVIC0tPiBSW0NhcmQgcmVhZGVyPGJyLz5ORkMgLyBVU0JdCiAgICBlbmQKICAgIFUgLS0-fHNoYXJlZCBEVE9zLCBSRVNUfCBDW0JhY2sgb2ZmaWNlIGJhY2tlbmRdCiAgICBNW01hbmFnZXIncyBwaG9uZSBhcHBdIC0tPnxzYW1lIERUT3N8IEM%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="828" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the board does have a GPU, set one build hint (or let the board profile choose it) and the same renderer composites through GBM, EGL and GLES 2. The frames are identical to the software output; the CPU time per frame drops. If the GPU driver turns out to be broken, the app logs why and falls back to software instead of showing a black screen.&lt;/p&gt;

&lt;p&gt;The screen code is the code you'd write for a phone. Your POS app, the manager's phone app and the back office share the same entity classes, so a change to the receipt format breaks the build rather than a store.&lt;/p&gt;

&lt;h3&gt;
  
  
  A robot vacuum: the headless flavor
&lt;/h3&gt;

&lt;p&gt;A robot vacuum needs a brain but no screen. The UI is the phone app. The device has to read its sensors, drive its motors, expose an API the app can find on the local network, accept only the owner's phone, take updates and keep working when the Wi-Fi drops. Every component inside it is multiplied by every unit sold, so memory and CPU headroom cost real money for the life of the product.&lt;/p&gt;

&lt;p&gt;That's the backend flavor: the same Codename One backend that runs your cloud server, compiled to a native executable for the board. REST endpoints, WebSockets, the ORM, MQTT, Bonjour discovery and mutual TLS for device identity are all there, next to the hardware API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBzdWJncmFwaCBWW1JvYm90IHZhY3V1bSwgaGVhZGxlc3NdCiAgICAgICAgQltDb2RlbmFtZSBPbmUgYmFja2VuZDxici8-bmF0aXZlIGV4ZWN1dGFibGVdIC0tPiBIW01vdG9ycywgYnVtcCBzZW5zb3JzLCBiYXR0ZXJ5PGJyLz5HUElPLCBJMkMsIFBXTV0KICAgICAgICBCIC0tPiBMW1JlYWwtdGltZSBtb3RvciBsb29wXQogICAgZW5kCiAgICBBW1Bob25lIGFwcF0gLS0-fEJvbmpvdXIsIHRoZW4gUkVTVCArIFdlYlNvY2tldDxici8-bXV0dWFsIFRMU3wgQgogICAgQiAtLT58b3V0Ym94IHdoZW4gb25saW5lfCBDW0Nsb3VkIGJhY2tlbmQ8YnIvPmZsZWV0LCB1cGRhdGVzXQogICAgQSAtLT58YXdheSBmcm9tIGhvbWV8IEM%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBzdWJncmFwaCBWW1JvYm90IHZhY3V1bSwgaGVhZGxlc3NdCiAgICAgICAgQltDb2RlbmFtZSBPbmUgYmFja2VuZDxici8-bmF0aXZlIGV4ZWN1dGFibGVdIC0tPiBIW01vdG9ycywgYnVtcCBzZW5zb3JzLCBiYXR0ZXJ5PGJyLz5HUElPLCBJMkMsIFBXTV0KICAgICAgICBCIC0tPiBMW1JlYWwtdGltZSBtb3RvciBsb29wXQogICAgZW5kCiAgICBBW1Bob25lIGFwcF0gLS0-fEJvbmpvdXIsIHRoZW4gUkVTVCArIFdlYlNvY2tldDxici8-bXV0dWFsIFRMU3wgQgogICAgQiAtLT58b3V0Ym94IHdoZW4gb25saW5lfCBDW0Nsb3VkIGJhY2tlbmQ8YnIvPmZsZWV0LCB1cGRhdGVzXQogICAgQSAtLT58YXdheSBmcm9tIGhvbWV8IEM%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1250" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A controller on the vacuum is a Spring-style class that reads the hardware directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/api/robot"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RobotController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;I2cDevice&lt;/span&gt; &lt;span class="n"&gt;fuelGauge&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;RobotController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;HardwareException&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fuelGauge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Hardware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openI2c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x36&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/battery"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;BatteryDto&lt;/span&gt; &lt;span class="nf"&gt;battery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;HardwareException&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BatteryDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fuelGauge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readRegister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x04&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The phone app compiles against the same &lt;code&gt;BatteryDto&lt;/code&gt;. It finds the vacuum with Bonjour when it's on the same network and goes through the cloud backend when it isn't, and the API is the same either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Competitors that need a GPU, gigabytes and a prayer
&lt;/h2&gt;

&lt;p&gt;We ran the same workload in five toolkits: an 800 by 480 gauge with a sweeping arc, a needle and a bold percentage, redrawn every frame and capped at 60 frames per second, in software. Same Debian arm64 container, three runs each, median shown.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fembedded-gui-memory.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="358" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fembedded-gui-memory.png" alt="Memory while animating, PSS in MB: Codename One: 18, Slint 23, Qt 23, JavaFX 116, Chromium 398, flutter-pi 112"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fembedded-gui-first-frame.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="358" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fembedded-gui-first-frame.png" alt="Milliseconds to the first frame from a cold start: Codename One: 46, Slint 45, Qt 73, JavaFX 327, Chromium 717, flutter-pi 767"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Toolkit&lt;/th&gt;
&lt;th&gt;First frame, cold&lt;/th&gt;
&lt;th&gt;Frames per second&lt;/th&gt;
&lt;th&gt;Memory (PSS)&lt;/th&gt;
&lt;th&gt;On disk (files mapped)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Codename One&lt;/td&gt;
&lt;td&gt;46 ms&lt;/td&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;td&gt;18 MB&lt;/td&gt;
&lt;td&gt;15 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slint 1.8&lt;/td&gt;
&lt;td&gt;45 ms&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;23 MB&lt;/td&gt;
&lt;td&gt;4.3 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qt 6.4 Widgets&lt;/td&gt;
&lt;td&gt;73 ms&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;23 MB&lt;/td&gt;
&lt;td&gt;73 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JavaFX 26&lt;/td&gt;
&lt;td&gt;327 ms&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;116 MB&lt;/td&gt;
&lt;td&gt;102 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chromium kiosk&lt;/td&gt;
&lt;td&gt;717 ms&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;398 MB&lt;/td&gt;
&lt;td&gt;378 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flutter-pi, GPU emulated on the CPU&lt;/td&gt;
&lt;td&gt;767 ms&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;112 MB&lt;/td&gt;
&lt;td&gt;233 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android (AOSP)&lt;/td&gt;
&lt;td&gt;needs a GPU and a BSP&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read that table as a hardware budget. The kiosk browser, the stack most teams reach for when they want "modern UX", needs 22 times our memory and 25 times our disk to draw one gauge, and takes three quarters of a second to show it. JavaFX, the other way to put Java on a board, needs six times our memory. Flutter does run without a GPU, but only by emulating one: it held its frame rate on 1.4 CPU cores, where we used 6% of one, in six times our memory and fifteen times our disk. Android can't run without a GPU at all. We're the only Java option in the table that fits on the board you'd actually want to pay for.&lt;/p&gt;

&lt;p&gt;The container ran on an Apple Silicon Mac, not a board, so a real board will be slower. Compare the rows against each other, not against your hardware. Slint needs less disk than we do, and Qt pushes a few more frames. Our form also draws more than the others: a title bar, a button, a slider and a label around the gauge. The flutter-pi row is a release build of the same gauge drawing to the kernel's virtual display, because flutter-pi needs a display device; the others drew to memory. LVGL isn't in the table either. It's a low-level C graphics library: its widgets are drawn objects you wire up by hand, with no component model, data binding, navigation, networking or storage around them. Comparing it with a full application toolkit wouldn't be a fair test of either.&lt;/p&gt;

&lt;p&gt;The headless flavor ran its own benchmark: a small JSON service pinned to two cores.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fembedded-server-throughput.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="358" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fembedded-server-throughput.png" alt="Peak JSON requests per second on two cores: Codename One: 379,930, Rust 290,204, Go 178,930, Node.js 63,488, Spring Boot 33,322, Python 6,684"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;Start&lt;/th&gt;
&lt;th&gt;Idle memory&lt;/th&gt;
&lt;th&gt;Peak requests/s&lt;/th&gt;
&lt;th&gt;p99 at peak&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Codename One backend&lt;/td&gt;
&lt;td&gt;40 ms&lt;/td&gt;
&lt;td&gt;9.2 MB&lt;/td&gt;
&lt;td&gt;379,900&lt;/td&gt;
&lt;td&gt;0.22 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rust, &lt;code&gt;axum&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;35 ms&lt;/td&gt;
&lt;td&gt;1.6 MB&lt;/td&gt;
&lt;td&gt;290,200&lt;/td&gt;
&lt;td&gt;0.25 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go, &lt;code&gt;net/http&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;48 ms&lt;/td&gt;
&lt;td&gt;5.2 MB&lt;/td&gt;
&lt;td&gt;178,900&lt;/td&gt;
&lt;td&gt;0.59 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;147 ms&lt;/td&gt;
&lt;td&gt;48 MB&lt;/td&gt;
&lt;td&gt;63,500&lt;/td&gt;
&lt;td&gt;1.1 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring Boot 3.5&lt;/td&gt;
&lt;td&gt;1,340 ms&lt;/td&gt;
&lt;td&gt;164 MB&lt;/td&gt;
&lt;td&gt;33,300&lt;/td&gt;
&lt;td&gt;3.3 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python, FastAPI&lt;/td&gt;
&lt;td&gt;270 ms&lt;/td&gt;
&lt;td&gt;42 MB&lt;/td&gt;
&lt;td&gt;6,700&lt;/td&gt;
&lt;td&gt;11 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rust and Go use less memory. But a Java backend that serves 1.3 times Rust's peak at a lower p99, and starts 33 times faster than Spring Boot in an eighteenth of its memory, changes what you can put in a vacuum. Like Rust, our server makes two system calls per request and allocates nothing but the object the handler returns. CI reruns this on arm64 with every change to the backend runtime and fails the build if we fall below Rust. The &lt;a href="https://github.com/codenameone/CodenameOne/tree/master/scripts/embedded/bench" rel="noopener noreferrer"&gt;harness&lt;/a&gt; is public, so check our Rust setup.&lt;/p&gt;
&lt;h2&gt;
  
  
  The only stack that goes from the vacuum to the browser
&lt;/h2&gt;

&lt;p&gt;Here's what I think matters more than any single number. Look at what each option covers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Device UI&lt;/th&gt;
&lt;th&gt;Headless device&lt;/th&gt;
&lt;th&gt;Real-time loop&lt;/th&gt;
&lt;th&gt;iOS and Android&lt;/th&gt;
&lt;th&gt;Desktop&lt;/th&gt;
&lt;th&gt;Web&lt;/th&gt;
&lt;th&gt;Cloud backend&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Codename One&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qt&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;Qt Core&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;WebAssembly&lt;/td&gt;
&lt;td&gt;HTTP server module&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slint&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;Android, iOS preview&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;WebAssembly&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flutter (flutter-pi)&lt;/td&gt;
&lt;td&gt;GPU, or emulated on the CPU&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chromium kiosk&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;as a web app&lt;/td&gt;
&lt;td&gt;as a web app&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android (AOSP)&lt;/td&gt;
&lt;td&gt;GPU only&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;Android only&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nobody else gives you a device UI, a headless device, a real-time control loop, the phone app, the desktop app, the web app and the cloud server from one language, one build and one set of classes. That's the "super full stack": the entities, the DTOs and the API contract live in a &lt;code&gt;shared&lt;/code&gt; module that every target compiles against. A field renamed on the vacuum is a compile error in the phone app, not a support ticket. One team can own the whole product, and one security fix lands everywhere at once.&lt;/p&gt;

&lt;p&gt;The rest of the ecosystem comes along: Bluetooth LE, NFC, MQTT, Bonjour, the ORM, push, crash reporting and the build tools you already use for mobile.&lt;/p&gt;
&lt;h2&gt;
  
  
  What it looks like in code
&lt;/h2&gt;

&lt;p&gt;Hardware access is in the core API. GPIO, I2C, SPI, serial, PWM, CAN, the watchdog and the backlight go through the kernel's own interfaces, with no extra library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Hardware&lt;/span&gt; &lt;span class="n"&gt;hw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Hardware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isSupported&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Hardware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;FEATURE_GPIO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;GpioPin&lt;/span&gt; &lt;span class="n"&gt;led&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openGpioOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"gpiochip0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;GpioPin&lt;/span&gt; &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openGpioInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"gpiochip0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nc"&gt;GpioPin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;BIAS_PULL_UP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;GpioPin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;EDGE_FALLING&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rising&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nanos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;led&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHigh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;led&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isHigh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HardwareException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isSupported&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Hardware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;FEATURE_I2C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;I2cDevice&lt;/span&gt; &lt;span class="n"&gt;sensor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openI2c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x48&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;p&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Sensor reads "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readRegister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HardwareException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a phone, &lt;code&gt;isSupported&lt;/code&gt; answers false, so the same class is safe everywhere. In the simulator it runs against a virtual board with 32 GPIO lines, a TMP102 temperature sensor, an EEPROM and loopback SPI, serial, and CAN. You can write and unit test the hardware code before the board arrives.&lt;/p&gt;

&lt;p&gt;Getting it onto the board is one command. It builds, copies the executable over SSH, installs it as a systemd service and follows the log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn cn1:runOnDevice -Dcn1.board=rpi5 -Dcn1.device.host=pi.local -Dcn1.device.user=pi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Board profiles cover the Raspberry Pi family, i.MX 8 and i.MX 6, RK3588, Jetson, BeaglePlay and generic arm64, armv7, x64 and RISC-V. There's a Yocto layer for production images, and a cross build can point at a Yocto or Buildroot SDK sysroot.&lt;/p&gt;

&lt;h2&gt;
  
  
  No royalties, and the support embedded actually needs
&lt;/h2&gt;

&lt;p&gt;Embedded UI is usually sold per unit. You ship ten thousand terminals and pay a royalty on each one, which is fine until the product succeeds and the toolkit becomes a line item on every unit you ship.&lt;/p&gt;

&lt;p&gt;Codename One doesn't charge royalties. It's open source under GPL 2 with the Classpath exception, on devices like everywhere else.&lt;/p&gt;

&lt;p&gt;What embedded teams need from a vendor is different. A device ships once and stays in the field for ten years or more. It runs the exact framework version it was certified with, and the team needs a security fix in that version five years from now, not an upgrade to whatever master looks like then. That's long-term support: a maintained branch for your product, backports of the fixes that matter to you, and someone who answers when the board vendor's new kernel breaks something.&lt;/p&gt;

&lt;p&gt;That runs far past the timeline we normally support, and it's work we're happy to do with you directly. It's a better deal for both sides than a royalty: you pay for the support you use, and the price doesn't grow with your success. If you're planning a product, &lt;a href="https://www.codenameone.com/contact-us.html" rel="noopener noreferrer"&gt;talk to us&lt;/a&gt; early so the branch matches your release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Linux today, other systems and RTOS on request
&lt;/h2&gt;

&lt;p&gt;We only support embedded Linux right now. If your product runs on Zephyr, QNX, VxWorks, FreeRTOS or a vendor OS, tell us. The renderer needs a frame buffer and the backend needs sockets, and ParparVM already emits portable C, so a new OS means porting two layers. Real demand decides which one comes next, and that includes a proper hard real-time RTOS.&lt;/p&gt;

&lt;h2&gt;
  
  
  More This Week
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Real-time threads
&lt;/h3&gt;

&lt;p&gt;A vacuum's motor loop, or a POS card reader's timing, has deadlines, and a garbage collector that stops the world is the usual reason people say Java can't meet them. Codename One now has real-time threads on PREEMPT_RT Linux that the collector never stops:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBUW2N5Y2xlIGluIHlvdXIgUmVhbHRpbWVUYXNrXSAtLT4gQntCdWlsZH0KICAgIEIgLS0-fGFsbG9jYXRlc3wgWFtCdWlsZCBmYWlscywgbmFtZXMgdGhlIGxpbmVdCiAgICBCIC0tPnxjbGVhbnwgTltOYXRpdmUgY29kZSwgbm8gSklUXQogICAgTiAtLT4gUltTQ0hFRF9GSUZPIHRocmVhZCwgcGlubmVkIGNvcmUsIGxvY2tlZCBtZW1vcnldCiAgICBHW0dhcmJhZ2UgY29sbGVjdG9yXSAtLi0-fG5ldmVyIHN0b3BzIGl0fCBS%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBUW2N5Y2xlIGluIHlvdXIgUmVhbHRpbWVUYXNrXSAtLT4gQntCdWlsZH0KICAgIEIgLS0-fGFsbG9jYXRlc3wgWFtCdWlsZCBmYWlscywgbmFtZXMgdGhlIGxpbmVdCiAgICBCIC0tPnxjbGVhbnwgTltOYXRpdmUgY29kZSwgbm8gSklUXQogICAgTiAtLT4gUltTQ0hFRF9GSUZPIHRocmVhZCwgcGlubmVkIGNvcmUsIGxvY2tlZCBtZW1vcnldCiAgICBHW0dhcmJhZ2UgY29sbGVjdG9yXSAtLi0-fG5ldmVyIHN0b3BzIGl0fCBS%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="958" height="289"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MotorLoop&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;RealtimeTask&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;gains&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// allocate up front&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;PwmChannel&lt;/span&gt; &lt;span class="n"&gt;pwm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                            &lt;span class="c1"&gt;// may allocate&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;pwm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Hardware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openPwm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HardwareException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;cycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                            &lt;span class="c1"&gt;// must not allocate&lt;/span&gt;
        &lt;span class="c1"&gt;// read sensors, compute gains, then pwm.setDutyCycle(...)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"motor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RealtimeParameters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPeriodNanos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPriority&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCpu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MotorLoop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;cycle()&lt;/code&gt; allocates, the build fails and names the method and the line. Every thread keeps latency statistics so you can prove the deadline on your own board. Monday's &lt;a href="https://www.codenameone.com/blog/real-time-java-control-loops/" rel="noopener noreferrer"&gt;real-time article&lt;/a&gt; explains the terms, what makes this hard for a managed language and how we handle each part.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dash: race through your city
&lt;/h3&gt;

Dash gameplay on a neon track.

&lt;p&gt;Dash is our second game template: an endless runner whose levels can start as a 2D side-scroller and swing into 3D mid-run. Pick two points on a real street map and the route becomes a level, street names and landmarks included:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;CityRoute&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CityRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Old Harbour"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;420&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;CityRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;STRAIGHT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Quay Street"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;310&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;CityRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LEFT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Rope Walk"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;LevelDef&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server replays every run to keep the leaderboards clean, and friends race the same track with voice chat. Saturday's &lt;a href="https://www.codenameone.com/blog/build-an-endless-runner-in-java/" rel="noopener noreferrer"&gt;Dash article&lt;/a&gt; has the details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pulse: heart rates from the wrist to the trainer
&lt;/h3&gt;

&lt;a href="https://www.codenameone.com/blog/release-2026-11-06/pulse-phone-trainer.mp4" rel="noopener noreferrer"&gt;Download the trainer video&lt;/a&gt;.

&lt;p&gt;Pulse is a gym and studio template built around health data. A member's watch or Bluetooth chest strap feeds a workout session, the readings go to the studio's backend, and the trainer sees every member's heart rate zone live. Past workouts come in from HealthKit or Health Connect through one API. Sunday's &lt;a href="https://www.codenameone.com/blog/launch-your-own-gym-app/" rel="noopener noreferrer"&gt;Pulse article&lt;/a&gt; walks through the health code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Xbox, and help with consoles
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;codename1.arg.windows.xbox=true&lt;/code&gt; builds the native Windows port as a UWP app for Xbox Developer Mode:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBHW1lvdXIgZ2FtZSBvciBhcHBdIC0tPiBQW05hdGl2ZSBXaW5kb3dzIHBvcnRdCiAgICBQIC0tPnx3aW5kb3dzLnhib3g9dHJ1ZXwgVVtVV1AgYXBwIGNvbnRhaW5lcjxici8-Q29yZVdpbmRvdywgbm8gZGVza3RvcCB3aW5kb3ddCiAgICBVIC0tPiBSW0RpcmVjdDJEIGludG8gYSBEaXJlY3QzRCBzd2FwIGNoYWluXQogICAgVSAtLT4gSVtDb250cm9sbGVyOiBELXBhZCBhbmQgc3RpY2sgdG8gZGlyZWN0aW9uczxici8-QSB0byBmaXJlLCBCIHRvIGJhY2tdCiAgICBVIC0tPiBTW1RWIG92ZXJzY2FuIGFzIHRoZSBkaXNwbGF5IHNhZmUgYXJlYV0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBHW1lvdXIgZ2FtZSBvciBhcHBdIC0tPiBQW05hdGl2ZSBXaW5kb3dzIHBvcnRdCiAgICBQIC0tPnx3aW5kb3dzLnhib3g9dHJ1ZXwgVVtVV1AgYXBwIGNvbnRhaW5lcjxici8-Q29yZVdpbmRvdywgbm8gZGVza3RvcCB3aW5kb3ddCiAgICBVIC0tPiBSW0RpcmVjdDJEIGludG8gYSBEaXJlY3QzRCBzd2FwIGNoYWluXQogICAgVSAtLT4gSVtDb250cm9sbGVyOiBELXBhZCBhbmQgc3RpY2sgdG8gZGlyZWN0aW9uczxici8-QSB0byBmaXJlLCBCIHRvIGJhY2tdCiAgICBVIC0tPiBTW1RWIG92ZXJzY2FuIGFzIHRoZSBkaXNwbGF5IHNhZmUgYXJlYV0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="921" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your game keeps reading &lt;code&gt;Display.getGameAction()&lt;/code&gt; as it does on a phone. We haven't run it on a real Xbox yet, and Developer Mode can't sell a game. Shipping on consoles needs SDKs behind NDAs, so Tuesday's &lt;a href="https://www.codenameone.com/blog/codename-one-on-xbox/" rel="noopener noreferrer"&gt;Xbox article&lt;/a&gt; is also a request: if you can open that door, talk to us.&lt;/p&gt;

&lt;h3&gt;
  
  
  Port your Spring code with fewer edits
&lt;/h3&gt;

&lt;p&gt;Porting real services to the Codename One backend keeps finding the small things a Spring developer expects. This round closed a batch of them, so code like this now moves over unchanged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@CrossOrigin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;origins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://shop.example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="no"&gt;LOG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="nf"&gt;place&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ResponseStatusException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"An order needs at least one line"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="no"&gt;MDC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"customer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="no"&gt;LOG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Placing order with {} lines"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Json.read(body, Order.class)&lt;/code&gt; and &lt;code&gt;Json.write(order)&lt;/code&gt; work through codecs generated at build time, &lt;code&gt;String.matches&lt;/code&gt; and &lt;code&gt;java.util.regex&lt;/code&gt; work, and &lt;code&gt;BigDecimal&lt;/code&gt;, &lt;code&gt;YearMonth&lt;/code&gt;, &lt;code&gt;Semaphore&lt;/code&gt; and &lt;code&gt;ConcurrentLinkedQueue&lt;/code&gt; are there. A cn1lib can now also ship its own controllers, entities, and migrations. Wednesday's &lt;a href="https://www.codenameone.com/blog/cn1libs-with-a-server-side/" rel="noopener noreferrer"&gt;backend article&lt;/a&gt; has the full list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dart debugging, a faster web port and JFX Central
&lt;/h3&gt;

&lt;a href="https://www.codenameone.com/blog/release-2026-11-06/JFXCentral-demo.mp4" rel="noopener noreferrer"&gt;Download the JFX Central video&lt;/a&gt;.

&lt;p&gt;That's &lt;a href="https://www.jfx-central.com/" rel="noopener noreferrer"&gt;JFX Central&lt;/a&gt;, a real JavaFX application written by JavaFX experts, running on an iPhone through our JavaFX layer. It isn't as polished as an app written for Codename One, but bindings, cells, CSS and remote images all work.&lt;/p&gt;

&lt;p&gt;Flutter apps now debug in Dart: the build rewrites the class files' line tables and adds a JSR-45 source map, so stack traces name your &lt;code&gt;.dart&lt;/code&gt; files and the standard Java debugger steps through Dart. The JavaScript port got faster too. It inlines getters and setters, fuses &lt;code&gt;StringBuilder&lt;/code&gt; chains into plain string operations and keeps every array packed. String building in our compute suite went from 99 to 68 ms, and every checksum still matches the JVM. Integer division by zero now throws &lt;code&gt;ArithmeticException&lt;/code&gt; in the browser, as Java says it should. Thursday's &lt;a href="https://www.codenameone.com/blog/debug-dart-in-a-java-debugger/" rel="noopener noreferrer"&gt;article&lt;/a&gt; covers all three.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.codenameone.com/developer-guide/embedded-linux/" rel="noopener noreferrer"&gt;embedded Linux chapter&lt;/a&gt; covers displays, the GPU path, board profiles, deployment, hardware access, the five connected-product shapes and the full benchmark with its method. The harness is in &lt;code&gt;scripts/embedded/bench&lt;/code&gt; in the &lt;a href="https://github.com/codenameone/CodenameOne" rel="noopener noreferrer"&gt;repository&lt;/a&gt; if you want to rerun it on your own board. I'd love to see those numbers.&lt;/p&gt;

&lt;p&gt;If you're building a device, tell me what board it runs on and what you're using today. And if your product needs an OS we don't support yet, that's exactly the conversation I want to have.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Caches, Certificates, Streaming Uploads and a Simulator That Types</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Thu, 12 Nov 2026 14:01:23 +0000</pubDate>
      <link>https://dev.to/codenameone/caches-certificates-streaming-uploads-and-a-simulator-that-types-3d1j</link>
      <guid>https://dev.to/codenameone/caches-certificates-streaming-uploads-and-a-simulator-that-types-3d1j</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpwx6vcurjwl1szn5b70m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpwx6vcurjwl1szn5b70m.jpg" alt="Backend code and the simulator with a virtual keyboard" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The templates this month (a bank, a streaming service, a messenger, a delivery service and a game) all have servers, and every server ran into something a modern web app takes for granted. This post collects what we added to the Codename One backend to fill those gaps. Several of them apply to the client too. Then there's the simulator, which can now show a virtual keyboard and record a video of your app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; 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 &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you write Spring Boot during the day, most of this will look familiar on purpose. When there's no reason to differ, the backend behaves the way Spring does.&lt;/p&gt;

&lt;h2&gt;
  
  
  A bounded cache
&lt;/h2&gt;

&lt;p&gt;Bastion looks up prices on every quote. Calling the price feed on every quote is slow and burns the feed's quota. Caching forever shows stale prices. The new &lt;a href="https://www.codenameone.com/developer-guide/backend-observability/#_application_caches" rel="noopener noreferrer"&gt;cache&lt;/a&gt; has a size bound, expiry, a removal listener and metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Price&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;PRICES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Price&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"prices"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maximumSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expireAfterWriteMillis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removalListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cause&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"evicted "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tokens for calling other services
&lt;/h2&gt;

&lt;p&gt;Your server calls other servers: a Google API with a service account, a billing service with client credentials. &lt;a href="https://www.codenameone.com/developer-guide/backend-security/#backend-security-outbound" rel="noopener noreferrer"&gt;&lt;code&gt;AccessTokenProvider&lt;/code&gt;&lt;/a&gt; fetches the token, caches it and refreshes it before it expires:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;AccessTokenProvider&lt;/span&gt; &lt;span class="nf"&gt;billing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AccessTokenProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ClientCredentialsGrant&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://auth.example.com/oauth/token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"orders-service"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"billing.client-secret"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"invoices:write"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same change added &lt;a href="https://www.codenameone.com/developer-guide/backend-security/#backend-security-migration" rel="noopener noreferrer"&gt;importing users from an existing identity store&lt;/a&gt;, so moving an app's accounts over doesn't mean asking everyone to reset a password.&lt;/p&gt;

&lt;h2&gt;
  
  
  Certificates without OpenSSL on the command line
&lt;/h2&gt;

&lt;p&gt;Banks and partners love mutual TLS and private certificate authorities. The &lt;a href="https://www.codenameone.com/developer-guide/backend-security/#backend-security-pki" rel="noopener noreferrer"&gt;PKI API&lt;/a&gt; parses PEM, validates a chain against your own anchors, creates certificate signing requests and reads and writes PKCS#12:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Certificate&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;anchors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Certificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listFromPem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;KeyFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/etc/app/partner-ca.pem"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Certificate&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CertificateValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anchors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"api.partner.example"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Certificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listFromPem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;presentedPem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Errors in one place
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.codenameone.com/developer-guide/backend-web/#backend-web-errors" rel="noopener noreferrer"&gt;&lt;code&gt;@RestControllerAdvice&lt;/code&gt; and &lt;code&gt;@ExceptionHandler&lt;/code&gt;&lt;/a&gt; turn exceptions into responses once, instead of a try and catch in every handler. &lt;a href="https://www.codenameone.com/developer-guide/backend-web/#backend-web-interceptors" rel="noopener noreferrer"&gt;Interceptors&lt;/a&gt; and &lt;a href="https://www.codenameone.com/developer-guide/backend-web/#backend-web-validation" rel="noopener noreferrer"&gt;validation&lt;/a&gt; came with the same change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestControllerAdvice&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApiErrors&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@ExceptionHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IllegalArgumentException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;HttpServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt; &lt;span class="nf"&gt;badInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IllegalArgumentException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HttpServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;HttpServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"X-Error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Streaming bodies and a proxy
&lt;/h2&gt;

&lt;p&gt;Reel's uploads are video files. Reading them into memory isn't an option. &lt;a href="https://www.codenameone.com/developer-guide/backend-web/#_large_uploads_and_downloads" rel="noopener noreferrer"&gt;&lt;code&gt;request.getBodyStream()&lt;/code&gt;&lt;/a&gt; hands you the body as it arrives, and large uploads, multipart ones included, spool to disk instead of the heap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/archive"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nc"&gt;InputStream&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBodyStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                 &lt;span class="c1"&gt;// store, hash or forward each block&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Collections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;singletonMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"bytes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.codenameone.com/developer-guide/backend-web/#_forwarding_to_another_service" rel="noopener noreferrer"&gt;&lt;code&gt;HttpProxy&lt;/code&gt;&lt;/a&gt; forwards a request to an internal service. It reaches only its target and the hosts you allow, so a crafted path can't turn your server into an open proxy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://images.internal:8443"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allowHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"cdn.internal"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/images/{name}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;HttpServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Response&lt;/span&gt; &lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// the same path and query, upstream&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  JSON codecs and java.time in the ORM
&lt;/h2&gt;

&lt;p&gt;These two apply to the client as well as the server. &lt;a href="https://www.codenameone.com/developer-guide/backend-web/#backend-json-contract" rel="noopener noreferrer"&gt;JSON mapping&lt;/a&gt; gained custom codecs through &lt;code&gt;JsonTypeAdapter&lt;/code&gt;, constructor based creators, generics, arrays and explicit null handling. A codec turns a type into JSON and back, here a money amount kept as a decimal string so it never passes through a &lt;code&gt;double&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MoneyAdapter&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;JsonTypeAdapter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Money&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;Object&lt;/span&gt; &lt;span class="nf"&gt;toJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Money&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDecimalString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Money&lt;/span&gt; &lt;span class="nf"&gt;fromJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IllegalArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"expected an amount"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Money&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put &lt;code&gt;@JsonAdapter(MoneyAdapter.class)&lt;/code&gt; on the field or the class and the mapper uses it.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.codenameone.com/developer-guide/annotation-sqlite-orm/#orm-native-dates" rel="noopener noreferrer"&gt;ORM now maps &lt;code&gt;java.time&lt;/code&gt;&lt;/a&gt; without an annotation: &lt;code&gt;Instant&lt;/code&gt; and &lt;code&gt;LocalDateTime&lt;/code&gt; as epoch milliseconds and &lt;code&gt;LocalDate&lt;/code&gt; as an epoch day, so a date column is a number you can index and compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Booking&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// epoch milliseconds&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt; &lt;span class="n"&gt;checkIn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// epoch day&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;LocalDateTime&lt;/span&gt; &lt;span class="n"&gt;pickup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// epoch milliseconds&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tests that start only what they need
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.codenameone.com/developer-guide/backend-testing/#_testing_one_slice_of_the_application" rel="noopener noreferrer"&gt;&lt;code&gt;@WebMvcTest&lt;/code&gt;&lt;/a&gt; starts one controller and the beans you list, not the whole application, and &lt;code&gt;MockMvc&lt;/code&gt; calls it without a socket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@WebMvcTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GreetingApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;includes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Greetings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GreetingSliceTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;MockMvc&lt;/span&gt; &lt;span class="n"&gt;mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;greetsWithOnlyTheControllerWired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;mvc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/greet/{name}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;andExpect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isOk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;andExpect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;jsonPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"$.greeting"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Hello, Ada"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same change defined a &lt;a href="https://www.codenameone.com/developer-guide/backend-observability/#_the_metric_schema" rel="noopener noreferrer"&gt;metric schema&lt;/a&gt; and &lt;a href="https://www.codenameone.com/developer-guide/backend-observability/#_probes_and_draining" rel="noopener noreferrer"&gt;probes&lt;/a&gt; for observability, so a dashboard means the same thing for every Codename One service.&lt;/p&gt;

&lt;p&gt;The backend is still a tech preview. The &lt;a href="https://www.codenameone.com/developer-guide/backend/" rel="noopener noreferrer"&gt;backend chapters&lt;/a&gt; of the developer guide cover each of these, with the code that the snippets above come from.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simulator that types
&lt;/h2&gt;

&lt;p&gt;Here's a bug you've probably shipped. The login form looks great in the simulator. On a phone, the keyboard slides up and covers the password field, or the layout jumps, and nobody noticed because the simulator never had a keyboard.&lt;/p&gt;

&lt;p&gt;Now it &lt;a href="https://www.codenameone.com/developer-guide/mcp-headless-api/#_simulator_keyboard_touches_and_recording" rel="noopener noreferrer"&gt;does&lt;/a&gt;. &lt;strong&gt;Simulate &amp;gt; Virtual Keyboard&lt;/strong&gt; slides up a keyboard styled after the skin's platform, and it behaves the way that platform does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;iOS:&lt;/strong&gt; the screen keeps its size, &lt;code&gt;Display.getInvisibleAreaUnderVKB()&lt;/code&gt; reports the keyboard height, and the focused field scrolls into view.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android, resize mode:&lt;/strong&gt; the form gets a &lt;code&gt;sizeChanged&lt;/code&gt; and lays out again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android, pan mode:&lt;/strong&gt; the whole screen shifts up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;/p&gt;The simulated iOS keyboard sliding up over a sign up form.Recorded by the simulator itself, driven over MCP by the script below.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;By default Android picks a mode per field, the way the real platform does. The &lt;strong&gt;Android: Resize&lt;/strong&gt; and &lt;strong&gt;Android: Pan&lt;/strong&gt; menu items, or the &lt;code&gt;androidSoftInput&lt;/code&gt; setting, force one so you can test both.&lt;/p&gt;

&lt;p&gt;The simulator also records. It writes MP4, MOV, WebM or an animated GIF, of the screen alone or inside the device skin, on a fixed frame clock so the video doesn't stutter when your machine is busy. Taps show touch indicators and can play a click, and you can add captions. That's a store preview, a bug report or a tweet, without a screen recorder.&lt;/p&gt;

&lt;p&gt;Both are also tools in the simulator's built in MCP server, so a coding agent can drive a whole flow and hand you the video:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sim_keyboard_set   {"mode": "ios"}
sim_record_start   {"path": "/tmp/signup.mp4", "frame": "screen"}
sim_record_caption {"text": "The keyboard slides up, the field stays visible"}
ui_snapshot        {}
sim_tap            {"nodeId": 41}
sim_keyboard_type  {"text": "ada@example.com"}
sim_keyboard_key   {"key": "next"}
sim_keyboard_type  {"text": "correct horse"}
sim_keyboard_key   {"key": "done"}
sim_record_stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's an emulation of each platform's keyboard behavior, not the native keyboard itself, so do a final pass on a device. But the covered password field shows up on your desk now, not in a one-star review. The &lt;a href="https://www.codenameone.com/developer-guide/mcp-headless-api/#_simulator_keyboard_touches_and_recording" rel="noopener noreferrer"&gt;simulator keyboard and recording section&lt;/a&gt; of the MCP chapter lists every tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  That's the week
&lt;/h2&gt;

&lt;p&gt;Flutter, Compose, Swing, JavaFX and Android code, a bank, a streaming service, phones without Google and a better backend. Next week, we keep going. The &lt;a href="https://www.codenameone.com/blog/flutter-better-in-java/" rel="noopener noreferrer"&gt;Flutter post&lt;/a&gt; links everything from this week in one place.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>GrapheneOS, LineageOS, /e/OS and Others Are Now Officially Supported</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Wed, 11 Nov 2026 15:03:44 +0000</pubDate>
      <link>https://dev.to/codenameone/grapheneos-lineageos-eos-and-others-are-now-officially-supported-3102</link>
      <guid>https://dev.to/codenameone/grapheneos-lineageos-eos-and-others-are-now-officially-supported-3102</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkhkjv30duh17ieeay3yy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkhkjv30duh17ieeay3yy.jpg" alt="An Android app running on Android and on Codename One side by side" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post is mostly about phones that don't have Google at all: GrapheneOS, LineageOS, CalyxOS, /e/OS and other Android builds. Codename One now supports them officially. After that, a shorter update on the Android compatibility layer, which runs existing Android apps on iPhone, desktop and the web.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; 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 &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  GrapheneOS, LineageOS, CalyxOS and /e/OS
&lt;/h2&gt;

&lt;p&gt;I was a member of Team OS/2. I know what it's like to use an operating system the big vendors forgot, and to find that half the apps you need just don't start. People choose GrapheneOS, LineageOS and other AOSP builds for good reasons, usually privacy, and they deserve apps that work. There's a business case too: when you support a niche operating system its users have learned to live without, you get the most loyal advocates your product will ever have. They notice, they remember, and they tell each other.&lt;/p&gt;

&lt;p&gt;Codename One apps mostly worked on these phones before. But mostly isn't support: call the wrong API and you could hit a failure, or behavior nobody could predict. Now it's official, with a clear boundary. One APK serves both kinds of phone: at run time it checks whether Google Play Services is there. No build hint, no flavor, no second build. The Android port never calls Play services, Play Core or Firebase without checking first, and CI enforces that, so a future change can't quietly break it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;With Google Play&lt;/th&gt;
&lt;th&gt;Without Google Play&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UI, storage, networking, SQLite, media&lt;/td&gt;
&lt;td&gt;Works&lt;/td&gt;
&lt;td&gt;Works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On device ML Kit models&lt;/td&gt;
&lt;td&gt;Works&lt;/td&gt;
&lt;td&gt;Works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Location and geofences&lt;/td&gt;
&lt;td&gt;Fused location&lt;/td&gt;
&lt;td&gt;Framework &lt;code&gt;LocationManager&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maps&lt;/td&gt;
&lt;td&gt;Google Maps&lt;/td&gt;
&lt;td&gt;Vector &lt;code&gt;MapView&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google sign in&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;td&gt;Browser OAuth flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push&lt;/td&gt;
&lt;td&gt;Firebase&lt;/td&gt;
&lt;td&gt;UnifiedPush (ntfy, NextPush, Sunup)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In-app purchase&lt;/td&gt;
&lt;td&gt;Play Billing&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integrity attestation, in-app review&lt;/td&gt;
&lt;td&gt;Works&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The location fallback fixed something on Google phones too: builds without Play location now get proximity alerts and background location through the framework API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Push, with one line
&lt;/h3&gt;

&lt;p&gt;Push needs one line of configuration, because the phone has no Firebase to deliver it. UnifiedPush lets the user pick the app that holds the push connection, called a distributor. A UnifiedPush endpoint is a standard Web Push subscription, so the Codename One push service delivers to it with the Web Push credential your app already has. You copy its public key into a build hint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="na"&gt;codename1.arg.android.unifiedPush.vapidKey&lt;/span&gt;&lt;span class="o"&gt;=&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="mi"&gt;87&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt; &lt;span class="no"&gt;VAPID&lt;/span&gt; &lt;span class="n"&gt;public&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your app code doesn't change, and neither does your server. The registration order is automatic:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBTW1B1c2hDbGllbnQgcmVnaXN0ZXJzXSAtLT4gR3tHb29nbGUgUGxheSBTZXJ2aWNlcz99CiAgICBHIC0tPnx5ZXN8IEZbRmlyZWJhc2UgQ2xvdWQgTWVzc2FnaW5nXQogICAgRyAtLT58bm98IEh7SHVhd2VpIEhNUz99CiAgICBIIC0tPnx5ZXN8IEtbSHVhd2VpIFB1c2ggS2l0XQogICAgSCAtLT58bm98IFV7VW5pZmllZFB1c2ggZGlzdHJpYnV0b3IgaW5zdGFsbGVkP30KICAgIFUgLS0-fHllc3wgUFtVbmlmaWVkUHVzaCwgZGVsaXZlcmVkIGFzIFdlYiBQdXNoXQogICAgVSAtLT58bm98IEVbcHVzaFJlZ2lzdHJhdGlvbkVycm9yOiBhIHB1c2ggc2VydmljZSBpcyBuZWVkZWRd%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBTW1B1c2hDbGllbnQgcmVnaXN0ZXJzXSAtLT4gR3tHb29nbGUgUGxheSBTZXJ2aWNlcz99CiAgICBHIC0tPnx5ZXN8IEZbRmlyZWJhc2UgQ2xvdWQgTWVzc2FnaW5nXQogICAgRyAtLT58bm98IEh7SHVhd2VpIEhNUz99CiAgICBIIC0tPnx5ZXN8IEtbSHVhd2VpIFB1c2ggS2l0XQogICAgSCAtLT58bm98IFV7VW5pZmllZFB1c2ggZGlzdHJpYnV0b3IgaW5zdGFsbGVkP30KICAgIFUgLS0-fHllc3wgUFtVbmlmaWVkUHVzaCwgZGVsaXZlcmVkIGFzIFdlYiBQdXNoXQogICAgVSAtLT58bm98IEVbcHVzaFJlZ2lzdHJhdGlvbkVycm9yOiBhIHB1c2ggc2VydmljZSBpcyBuZWVkZWRd%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="614" height="1130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The one real gap is in-app purchase. Play Billing needs the Play Store. If selling on de-Googled phones matters to you, tell us; it's something we can add.&lt;/p&gt;

&lt;p&gt;To test, use the Android emulator's AOSP system images, the &lt;code&gt;default&lt;/code&gt; target, which have no Google apps. The &lt;a href="https://www.codenameone.com/developer-guide/android-without-google-play/" rel="noopener noreferrer"&gt;Android without Google Play chapter&lt;/a&gt; has the details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also new: classic Android apps, on iPhone
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.codenameone.com/blog/android-apps-beyond-android/" rel="noopener noreferrer"&gt;Three weeks ago&lt;/a&gt; we showed that an Android Studio module's &lt;code&gt;src/main&lt;/code&gt; can build unmodified as a Codename One app. There's no Android runtime inside it. Views are Codename One components, layouts follow Android's measure and layout rules, and resources compile to a binary table at build time. Nothing parses XML or uses reflection on the device. You import a project with one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn cn1:import-android-project -Dcn1.android.import=/path/to/MyAndroidApp
mvn cn1:build -Dcodename1.platform=ios
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/developer-guide/img/android-interop-material.png" rel="noopener noreferrer"&gt;&lt;img width="736" height="800" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fandroid-interop-material.png" alt="A Material 3 screen on Android and on Codename One"&gt;&lt;/a&gt;Material 3.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/android-interop-recycler.png" rel="noopener noreferrer"&gt;&lt;img width="736" height="800" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fandroid-interop-recycler.png" alt="A RecyclerView on Android and on Codename One"&gt;&lt;/a&gt;RecyclerView.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/android-interop-kotlin.png" rel="noopener noreferrer"&gt;&lt;img width="736" height="800" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fandroid-interop-kotlin.png" alt="A Kotlin activity on Android and on Codename One"&gt;&lt;/a&gt;Kotlin activities.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Each pair shows Android on the left and Codename One on the right, from the same source.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's new: the parts real apps lean on
&lt;/h3&gt;

&lt;p&gt;Toy apps are mostly UI. Real apps store data, run work in the background and talk to the hardware. The layer now covers those:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Room.&lt;/strong&gt; The build generates the &lt;code&gt;_Impl&lt;/code&gt; classes, the way Android's annotation processor does. Features we don't support yet (&lt;code&gt;@Relation&lt;/code&gt;, &lt;code&gt;suspend&lt;/code&gt; DAOs, Flow, Rx, Paging, &lt;code&gt;@AutoMigration&lt;/code&gt; and FTS) fail the build and name the member.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WorkManager.&lt;/strong&gt; Chains, unique work, retries and LiveData status, persisted across app restarts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Location and sensors.&lt;/strong&gt; &lt;code&gt;LocationManager&lt;/code&gt; and &lt;code&gt;SensorManager&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Services.&lt;/strong&gt; Manifest services, including &lt;code&gt;startForeground()&lt;/code&gt; mapped to Codename One's &lt;code&gt;ForegroundService&lt;/code&gt;. Notifications from &lt;code&gt;NotificationManager&lt;/code&gt; and &lt;code&gt;NotificationCompat&lt;/code&gt; show as local notifications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keystore.&lt;/strong&gt; AES keys from &lt;code&gt;AndroidKeyStore&lt;/code&gt; with GCM and CBC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document pickers.&lt;/strong&gt; &lt;code&gt;ACTION_OPEN_DOCUMENT&lt;/code&gt;, &lt;code&gt;ACTION_CREATE_DOCUMENT&lt;/code&gt; and &lt;code&gt;ACTION_GET_CONTENT&lt;/code&gt;, plus the ActivityResult contracts, open the platform's native open and save dialogs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So this is ordinary Android code, and it now runs on an iPhone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Dao&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;NoteDao&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT * FROM Note ORDER BY created DESC"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Note&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Insert&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Note&lt;/span&gt; &lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;Intent&lt;/span&gt; &lt;span class="n"&gt;save&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ACTION_CREATE_DOCUMENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;CATEGORY_OPENABLE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;putExtra&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;EXTRA_TITLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"notes.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;startActivityForResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;SAVE_NOTES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Where iOS says no
&lt;/h3&gt;

&lt;p&gt;Some Android behavior has no iOS equivalent, and the &lt;a href="https://www.codenameone.com/developer-guide/android-interop/" rel="noopener noreferrer"&gt;Android chapter&lt;/a&gt; has a table for it. Location in a foreground service keeps running in the background when the manifest asks for background location. Other foreground service work gets only the short window iOS allows. Constrained WorkManager jobs go to iOS's background task scheduler, and iOS decides when they run. On iOS, &lt;code&gt;ACTION_CREATE_DOCUMENT&lt;/code&gt; returns &lt;code&gt;RESULT_OK&lt;/code&gt; right away and shows the save dialog when your app closes the output stream, so a cancel isn't reported back.&lt;/p&gt;

&lt;p&gt;Other limits: Keystore keys aren't hardware backed there, &lt;code&gt;Parcel&lt;/code&gt; is an in process container, SQLite uses a single connection, there are no system content providers or broadcasts, and reflection based libraries won't run on iOS.&lt;/p&gt;

&lt;p&gt;Tomorrow, the last post of the week: &lt;a href="https://www.codenameone.com/blog/backend-caches-certificates-and-a-simulator-that-types/" rel="noopener noreferrer"&gt;Caches, Certificates, Streaming Uploads and a Simulator That Types&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Reel: Your Own Streaming Service or Course Platform</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Tue, 10 Nov 2026 14:36:01 +0000</pubDate>
      <link>https://dev.to/codenameone/reel-your-own-streaming-service-or-course-platform-4i2d</link>
      <guid>https://dev.to/codenameone/reel-your-own-streaming-service-or-course-platform-4i2d</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu9csyuf2efz85goj80y3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu9csyuf2efz85goj80y3.jpg" alt="The Reel streaming app catalog and player" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Video is how a lot of businesses now talk to their customers. A gym sells classes, a university publishes lectures, a creator community wants its own home instead of renting space on someone else's platform. They all need the same pieces: a catalog, a player that remembers where you stopped, uploads, moderation, payments and some protection for paid content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; 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 &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reel&lt;/strong&gt; is those pieces, the app and its server in one Java project. Like last week's &lt;a href="https://www.codenameone.com/blog/rewrite-everything-in-java-not-rust/" rel="noopener noreferrer"&gt;Skirmish, Parley and Savor&lt;/a&gt;, it's a template you generate and brand, not a demo you admire.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/developer-guide/img/reel-viewer-home.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="1422" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Freel-viewer-home.png" alt="The Reel catalog"&gt;&lt;/a&gt;The catalog.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/reel-viewer-player-tracks.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="1422" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Freel-viewer-player-tracks.png" alt="The Reel player with audio and subtitle tracks"&gt;&lt;/a&gt;Audio and subtitle tracks.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/reel-viewer-course.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="1422" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Freel-viewer-course.png" alt="A Reel course with its lessons"&gt;&lt;/a&gt;A course and its lessons.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/reel-moderation-queue.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="1422" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Freel-moderation-queue.png" alt="The Reel moderation queue"&gt;&lt;/a&gt;The moderation queue.&lt;p&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One template, three businesses
&lt;/h2&gt;

&lt;p&gt;Reel's behavior is driven by presets you switch at run time, with no rebuild:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Subscription service.&lt;/strong&gt; Plans or rentals, profiles, downloads, resume across devices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creator platform.&lt;/strong&gt; Anyone with the creator role uploads video, the server packages it, and moderators approve it before it's public. Think of a community YouTube for your niche.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Academy.&lt;/strong&gt; Courses, lessons and quizzes, for training and tutorials.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of the preset, toggles turn ads, rentals, downloads, uploads, courses, quizzes and polls on or off. The server assigns roles (viewer, creator, moderator and admin) and the app shows each person the screens their role allows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uploads and moderation
&lt;/h2&gt;

&lt;p&gt;Creators upload from the app and the server packages each upload for streaming and checks it against blocklists the moderators maintain. A title only goes live after a moderator approves it, and a moderator can take it down later. Takedowns take effect mid-stream: the player's heartbeat checks with the server, so a removed title stops playing instead of running to the end.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBDW0NyZWF0b3IgdXBsb2Fkc10gLS0-IFBbU2VydmVyIHBhY2thZ2VzIEhMU10KICAgIFAgLS0-IEJbQmxvY2tsaXN0IGNoZWNrc10KICAgIEIgLS0-IFFbTW9kZXJhdGlvbiBxdWV1ZV0KICAgIFEgLS0-fGFwcHJvdmV8IExbTGl2ZSBpbiB0aGUgY2F0YWxvZ10KICAgIFEgLS0-fHJlamVjdHwgUltCYWNrIHRvIHRoZSBjcmVhdG9yXQogICAgTCAtLT58dGFrZWRvd258IFhbU3RvcHMgb24gdGhlIG5leHQgaGVhcnRiZWF0XQ%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBDW0NyZWF0b3IgdXBsb2Fkc10gLS0-IFBbU2VydmVyIHBhY2thZ2VzIEhMU10KICAgIFAgLS0-IEJbQmxvY2tsaXN0IGNoZWNrc10KICAgIEIgLS0-IFFbTW9kZXJhdGlvbiBxdWV1ZV0KICAgIFEgLS0-fGFwcHJvdmV8IExbTGl2ZSBpbiB0aGUgY2F0YWxvZ10KICAgIFEgLS0-fHJlamVjdHwgUltCYWNrIHRvIHRoZSBjcmVhdG9yXQogICAgTCAtLT58dGFrZWRvd258IFhbU3RvcHMgb24gdGhlIG5leHQgaGVhcnRiZWF0XQ%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1334" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A streaming API with DRM, in core
&lt;/h2&gt;

&lt;p&gt;Reel needed playback for protected video, and that belongs in the framework, not in one template. So Codename One now has a streaming API in &lt;code&gt;com.codename1.media&lt;/code&gt;. It's small on purpose:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;StreamRequest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;An HLS manifest or a progressive file URL, plus a start position, request headers, a completion callback and an optional DRM configuration. &lt;code&gt;create()&lt;/code&gt; or &lt;code&gt;createAsync()&lt;/code&gt; returns an ordinary &lt;code&gt;Media&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DrmConfig&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The scheme and license server URL, headers sent with every license request, a FairPlay certificate URL and a content id.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DrmScheme&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;AES_128&lt;/code&gt; (encrypted HLS), &lt;code&gt;CLEARKEY&lt;/code&gt;, &lt;code&gt;WIDEVINE&lt;/code&gt; and &lt;code&gt;FAIRPLAY&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MediaManager.isDrmSupported(scheme)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Whether this device can play a scheme, asked before you open the stream.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Opening a protected stream that resumes where the viewer stopped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;StreamRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StreamRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manifestUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setStartPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resumeSeconds&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDrm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DrmConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DrmScheme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;CLEARKEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;licenseUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addLicenseHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"X-Reel-License"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;licenseToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BorderLayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;CENTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getVideoComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revalidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each port maps the scheme to what the device has: AVPlayer decrypts AES-128 HLS and plays FairPlay on Apple, and Android's player and &lt;code&gt;MediaDrm&lt;/code&gt; handle AES-128 HLS, ClearKey, and Widevine where the device has it. Devices differ, so Reel asks first and sends the server the schemes this device can play, strongest first. The server picks one and returns the matching manifest and license URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;DrmScheme&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;DrmScheme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;WIDEVINE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;DrmScheme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;FAIRPLAY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;DrmScheme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;CLEARKEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;DrmScheme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;AES_128&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;iter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;iter&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MediaManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isDrmSupported&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBBcHAKICAgIHBhcnRpY2lwYW50IFNlcnZlcgogICAgcGFydGljaXBhbnQgTGljZW5zZSBhcyBMaWNlbnNlIHNlcnZlcgogICAgQXBwLT4-QXBwOiBpc0RybVN1cHBvcnRlZCBmb3IgZWFjaCBzY2hlbWUKICAgIEFwcC0-PlNlcnZlcjogcGxheSB0aXRsZSwgc3VwcG9ydGVkIHNjaGVtZXMKICAgIFNlcnZlci0tPj5BcHA6IG1hbmlmZXN0IFVSTCwgc2NoZW1lLCBsaWNlbnNlIFVSTCwgbWVkaWEgdG9rZW4KICAgIEFwcC0-PkxpY2Vuc2U6IGxpY2Vuc2UgcmVxdWVzdCArIFgtUmVlbC1MaWNlbnNlCiAgICBMaWNlbnNlLS0-PkFwcDoga2V5IChDbGVhcktleSkgb3IgbGljZW5zZSAoV2lkZXZpbmUsIEZhaXJQbGF5KQogICAgbG9vcCBldmVyeSBmZXcgc2Vjb25kcwogICAgICAgIEFwcC0-PlNlcnZlcjogaGVhcnRiZWF0LCBwb3NpdGlvbgogICAgICAgIFNlcnZlci0tPj5BcHA6IGtlZXAgcGxheWluZywgb3Igc3RvcCAodGFrZWRvd24sIHN0cmVhbSBsaW1pdCkKICAgIGVuZA%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBBcHAKICAgIHBhcnRpY2lwYW50IFNlcnZlcgogICAgcGFydGljaXBhbnQgTGljZW5zZSBhcyBMaWNlbnNlIHNlcnZlcgogICAgQXBwLT4-QXBwOiBpc0RybVN1cHBvcnRlZCBmb3IgZWFjaCBzY2hlbWUKICAgIEFwcC0-PlNlcnZlcjogcGxheSB0aXRsZSwgc3VwcG9ydGVkIHNjaGVtZXMKICAgIFNlcnZlci0tPj5BcHA6IG1hbmlmZXN0IFVSTCwgc2NoZW1lLCBsaWNlbnNlIFVSTCwgbWVkaWEgdG9rZW4KICAgIEFwcC0-PkxpY2Vuc2U6IGxpY2Vuc2UgcmVxdWVzdCArIFgtUmVlbC1MaWNlbnNlCiAgICBMaWNlbnNlLS0-PkFwcDoga2V5IChDbGVhcktleSkgb3IgbGljZW5zZSAoV2lkZXZpbmUsIEZhaXJQbGF5KQogICAgbG9vcCBldmVyeSBmZXcgc2Vjb25kcwogICAgICAgIEFwcC0-PlNlcnZlcjogaGVhcnRiZWF0LCBwb3NpdGlvbgogICAgICAgIFNlcnZlci0tPj5BcHA6IGtlZXAgcGxheWluZywgb3Igc3RvcCAodGFrZWRvd24sIHN0cmVhbSBsaW1pdCkKICAgIGVuZA%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="873" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Out of the box, Reel packages each video once, as AES-128 HLS and as CENC encrypted MP4 for ClearKey, under one key. Keys are issued only against a license token tied to the session, and every playlist, segment and key request carries a media token that expires and is scoped to one video and a maximum quality. Concurrent stream limits are enforced by the heartbeat. Protected video also blocks screenshots and screen recording, and can add a watermark with the account name.&lt;/p&gt;

&lt;p&gt;I'll be direct about the limit: AES-128 and ClearKey stop casual copying, not a determined attacker, because the key reaches the device. For studio grade protection you plug in a Widevine or FairPlay license vendor through &lt;code&gt;reel.drm.widevine.url&lt;/code&gt; and &lt;code&gt;reel.drm.fairplay.url&lt;/code&gt;. The server forwards the license challenge, and the app code doesn't change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audio tracks and subtitles
&lt;/h2&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/developer-guide/img/reel-viewer-lesson-subtitles.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="1422" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Freel-viewer-lesson-subtitles.png" alt="A Reel lesson playing with Spanish subtitles"&gt;&lt;/a&gt;Subtitles over a lesson.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/reel-viewer-settings-subtitles.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="1422" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Freel-viewer-settings-subtitles.png" alt="Reel subtitle language and size settings"&gt;&lt;/a&gt;Language and size settings.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/reel-studio-video-subtitles.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="1422" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Freel-studio-video-subtitles.png" alt="Adding a WebVTT subtitle track in the Reel studio"&gt;&lt;/a&gt;A creator adds a track per language.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Creators add a subtitle track per language in the studio, as WebVTT. The server checks that it really is WebVTT and stores it beside the video, not inside the stream.&lt;/p&gt;

&lt;p&gt;That last part is a deliberate choice. A platform player can render text tracks, but each one finds them differently, styles them differently, and some can't see a track inside an encrypted stream at all. So Reel draws subtitles itself, over the picture, and they look the same on every device and under every DRM scheme. The parser is about a hundred lines of plain Java in the template: it reads each cue's start and end time and its text, and drops WebVTT's positioning and inline tags such as &lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;v Speaker&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Captions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loaded&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wanted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subtitleLanguage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;captions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loaded&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;updateCaption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;updateCaption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;captions&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;captions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;captions.at(millis)&lt;/code&gt; returns the cues that cover that moment, one per line, and the player calls it as the position moves. Switching subtitles in the player's &lt;strong&gt;Audio &amp;amp; subtitles&lt;/strong&gt; sheet takes effect at once, because nothing about the stream changes. Switching the audio language or the quality reopens the stream at the same position, and the server marks the viewer's preferred audio track as the playlist default, so it starts in the right language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Payments and the rest
&lt;/h2&gt;

&lt;p&gt;Payments are simulated in the dev profile; the test card 4242 4242 4242 4242 goes through. Set &lt;code&gt;reel.payments.provider=stripe&lt;/code&gt; for Stripe, and store purchases are verified with Apple and Google. Push goes through the Codename One push service. Reel also runs in a browser with nothing installed, which is how many viewers will first meet your service.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/developer-guide/img/reel-viewer-home-desktop.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="470" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Freel-viewer-home-desktop.png" alt="The Reel catalog on a desktop screen"&gt;&lt;/a&gt;The same app on a desktop.&lt;p&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Generate Reel from the &lt;a href="https://www.codenameone.com/initializr/" rel="noopener noreferrer"&gt;Initializr&lt;/a&gt;. The &lt;a href="https://www.codenameone.com/developer-guide/reel-users-guide/" rel="noopener noreferrer"&gt;user's guide&lt;/a&gt; walks through every screen, and the &lt;a href="https://www.codenameone.com/developer-guide/reel-implementors-guide/" rel="noopener noreferrer"&gt;implementor's guide&lt;/a&gt; covers the packaging pipeline, DRM vendors and what to change before you launch. The template source is free under MIT-0, with no royalties and no revenue share.&lt;/p&gt;

&lt;p&gt;Tomorrow: &lt;a href="https://www.codenameone.com/blog/android-apps-everywhere-even-without-google/" rel="noopener noreferrer"&gt;GrapheneOS, LineageOS, /e/OS and Others Are Now Officially Supported&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Bastion: A Hardened Bank Template, and Crypto Wallets in Core</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Mon, 09 Nov 2026 14:01:15 +0000</pubDate>
      <link>https://dev.to/codenameone/bastion-a-hardened-bank-template-and-crypto-wallets-in-core-3i06</link>
      <guid>https://dev.to/codenameone/bastion-a-hardened-bank-template-and-crypto-wallets-in-core-3i06</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnm6f0w0zq3fwtycxft7m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnm6f0w0zq3fwtycxft7m.jpg" alt="The Bastion banking app and its Security Center" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A large share of the teams building on Codename One are banks and financial institutions. They don't ask me for a prettier list component. They ask how to pass a penetration test, what the auditor will want to see, and which protections actually work on which device.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; 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 &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bastion&lt;/strong&gt; is my answer, written as code. It's a virtual bank and a small exchange, the app and its server in one Java project. Customers hold dollars, euros, bitcoin and ether, move money, issue and freeze cards, trade, and withdraw coins to addresses they've allowed in advance. Staff verify the ledger and the audit trail. It's a template you can brand, but mostly it's a reference: every hardening technique in it is there so you can read how it's done.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/developer-guide/img/bastion-dashboard.png" rel="noopener noreferrer"&gt;&lt;img width="420" height="860" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fbastion-dashboard.png" alt="The Bastion dashboard"&gt;&lt;/a&gt;Accounts in four currencies.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/bastion-order-book.png" rel="noopener noreferrer"&gt;&lt;img width="420" height="860" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fbastion-order-book.png" alt="The BTC/USD market with candles and the order book"&gt;&lt;/a&gt;The BTC/USD market.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/bastion-withdraw.png" rel="noopener noreferrer"&gt;&lt;img width="420" height="860" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fbastion-withdraw.png" alt="Withdrawing coins to an allow-listed address"&gt;&lt;/a&gt;Withdrawals to allowed addresses.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/bastion-security-center.png" rel="noopener noreferrer"&gt;&lt;img width="420" height="860" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fbastion-security-center.png" alt="The Bastion Security Center"&gt;&lt;/a&gt;What is on, on this device.&lt;p&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Defense in layers
&lt;/h2&gt;

&lt;p&gt;No single protection makes an app secure. Bastion stacks them, so an attacker who gets past one meets the next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signing in.&lt;/strong&gt; OAuth authorization code with PKCE, five minute access tokens and rotating twelve hour refresh tokens. A TOTP second factor with ten recovery codes, plus WebAuthn passkeys. Once an account has a passkey, Bastion requires the strongest factor it has. Lockouts and per address rate limits slow down guessing, and changing the password ends every other session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The device.&lt;/strong&gt; On first sign in, the app generates a P-256 key pair, registers the public key with the server, and seals the private key with AES-256-GCM under a key kept in the Keychain or Keystore. The app locks after 30 seconds in the background or two minutes idle, and unlocks with biometrics, then a PIN, then a full sign in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every request that moves money is signed.&lt;/strong&gt; Not just authenticated with a token. Signed with the device key, over a canonical form that includes a timestamp and a single use nonce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SignedRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;canonical&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Hash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sha256&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;idempotencyKey&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;der&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Signature&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sign&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Signature&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SHA256_WITH_ECDSA&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;privateKey&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;SignedRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deviceId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;encodeUrlSafe&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jose&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;der&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A stolen access token can't move money without the device key. A captured request can't be replayed, because the nonce is spent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBBcHAKICAgIHBhcnRpY2lwYW50IFNlcnZlcgogICAgQXBwLT4-QXBwOiBjYW5vbmljYWwgZm9ybTogbWV0aG9kLCBwYXRoLCBib2R5IGhhc2gsIG5vbmNlLCB0aW1lCiAgICBBcHAtPj5BcHA6IHNpZ24gd2l0aCB0aGUgZGV2aWNlIGtleSAoUC0yNTYpCiAgICBBcHAtPj5TZXJ2ZXI6IHJlcXVlc3QgKyB0b2tlbiArIEJhc3Rpb24tU2lnbmF0dXJlCiAgICBTZXJ2ZXItPj5TZXJ2ZXI6IHRva2VuIHZhbGlkPyBkZXZpY2UgcmVnaXN0ZXJlZD8KICAgIFNlcnZlci0-PlNlcnZlcjogc2lnbmF0dXJlIHZhbGlkPyBub25jZSB1bnVzZWQ_IGNsb2NrIGluIHJhbmdlPwogICAgU2VydmVyLT4-U2VydmVyOiBsaW1pdHMsIHBheWVlIGNvb2xpbmctb2ZmLCBzdGVwLXVwCiAgICBTZXJ2ZXItLT4-QXBwOiBwb3N0ZWQgdG8gdGhlIGxlZGdlciwgYXBwZW5kZWQgdG8gdGhlIGF1ZGl0IGNoYWlu%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBBcHAKICAgIHBhcnRpY2lwYW50IFNlcnZlcgogICAgQXBwLT4-QXBwOiBjYW5vbmljYWwgZm9ybTogbWV0aG9kLCBwYXRoLCBib2R5IGhhc2gsIG5vbmNlLCB0aW1lCiAgICBBcHAtPj5BcHA6IHNpZ24gd2l0aCB0aGUgZGV2aWNlIGtleSAoUC0yNTYpCiAgICBBcHAtPj5TZXJ2ZXI6IHJlcXVlc3QgKyB0b2tlbiArIEJhc3Rpb24tU2lnbmF0dXJlCiAgICBTZXJ2ZXItPj5TZXJ2ZXI6IHRva2VuIHZhbGlkPyBkZXZpY2UgcmVnaXN0ZXJlZD8KICAgIFNlcnZlci0-PlNlcnZlcjogc2lnbmF0dXJlIHZhbGlkPyBub25jZSB1bnVzZWQ_IGNsb2NrIGluIHJhbmdlPwogICAgU2VydmVyLT4-U2VydmVyOiBsaW1pdHMsIHBheWVlIGNvb2xpbmctb2ZmLCBzdGVwLXVwCiAgICBTZXJ2ZXItLT4-QXBwOiBwb3N0ZWQgdG8gdGhlIGxlZGdlciwgYXBwZW5kZWQgdG8gdGhlIGF1ZGl0IGNoYWlu%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="802" height="691"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The network.&lt;/strong&gt; The app refuses cleartext, talks only to an allow list of hosts, and pins their certificates. That's &lt;code&gt;AppShield&lt;/code&gt;, a Codename One API, configured in a few lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ShieldConfig&lt;/span&gt; &lt;span class="n"&gt;shield&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ShieldConfig&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;collectSignals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AppConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ALLOWED_HOSTS&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;shield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;protect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HostPolicy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ENFORCED&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;AppShield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shield&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The screen.&lt;/strong&gt; Secure windows that keep sensitive screens out of screenshots and the app switcher, tapjacking protection, and a clipboard that clears itself after you copy an account number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limits.&lt;/strong&gt; Per transfer and daily limits, a cooling-off period for new payees, step up authentication above a threshold, and an allow list for crypto withdrawals.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Security Center tells the truth
&lt;/h2&gt;

&lt;p&gt;Most security settings screens show a column of green checks. Bastion's Security Center shows what's actually on for this device and this build. Some protections need a cloud build: code obfuscation with string encryption and control flow flattening (&lt;code&gt;cn1.hardenLevel=aggressive&lt;/code&gt;), Play Integrity, jailbreak detection, and blocking screenshots when the app goes to the background. When those aren't active, the Security Center labels them "Cloud build" rather than showing a green check. A test keeps that screen in sync with the build hints, so it can't drift into a lie.&lt;/p&gt;

&lt;h2&gt;
  
  
  The back office auditors want
&lt;/h2&gt;

&lt;p&gt;Money is a double entry ledger, not a balance column. The admin console has the journal, T-accounts, a trial balance, a balance sheet, an income statement, reconciliation, period close and a maker checker approvals queue. Every event lands in a hash chained audit log, and the console can verify the chain and export evidence as CSV. A read only auditor role sees all of it, on a desktop or on a phone.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/developer-guide/img/bastion-console-overview.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="500" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fbastion-console-overview.png" alt="The Bastion admin console overview on the desktop"&gt;&lt;/a&gt;The back office console.&lt;p&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Crypto wallets, exchanges and custody, in core
&lt;/h2&gt;

&lt;p&gt;I'm not a fan of crypto currency. I know plenty of people who are, and a few of them are customers. We're here to serve, and if we serve, we should be the best at it. So this isn't a wallet library bolted on the side. It's a big feature: everything a wallet, an exchange or a custodian needs, in core, with no third party library, and the same classes run in the app and in a Codename One backend.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;What's in it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;crypto.hd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;BIP-39 recovery phrases, BIP-32 and SLIP-10 derivation, &lt;code&gt;DerivationPath&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;crypto.evm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;EIP-55 addresses; legacy, EIP-2930 and EIP-1559 transactions; EIP-191 and EIP-712 signing; the Solidity ABI and ERC-20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;crypto.bitcoin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;P2PKH, P2SH, P2WPKH, P2WSH and Taproot addresses, scripts, transactions, PSBT and WIF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;crypto.solana&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transactions, the system and SPL token programs, priority fees&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;crypto.wallet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Wallet&lt;/code&gt;, &lt;code&gt;Signer&lt;/code&gt;, &lt;code&gt;SeedSigner&lt;/code&gt; and &lt;code&gt;VaultSigner&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;crypto.rpc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;EVM JSON-RPC, Esplora for Bitcoin, Solana JSON-RPC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;crypto.market&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Price feeds: CoinGecko, any JSON price API, and a caching decorator&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  A wallet
&lt;/h3&gt;

&lt;p&gt;One recovery phrase gives you addresses on every chain, and they match MetaMask, Electrum, Sparrow and Phantom on the standard paths, so a user can restore the same phrase in another wallet and find the same money:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Mnemonic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;generate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;           &lt;span class="c1"&gt;// 24 words&lt;/span&gt;
&lt;span class="nc"&gt;Wallet&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Wallet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromMnemonic&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;EvmAddress&lt;/span&gt; &lt;span class="n"&gt;eth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEvmAddress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;                 &lt;span class="c1"&gt;// m/44'/60'/0'/0/0&lt;/span&gt;
&lt;span class="nc"&gt;BitcoinAddress&lt;/span&gt; &lt;span class="n"&gt;btc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBitcoinAddress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BitcoinNetwork&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MAINNET&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;Wallet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BIP84&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;                              &lt;span class="c1"&gt;// bc1q...&lt;/span&gt;
&lt;span class="nc"&gt;SolanaAddress&lt;/span&gt; &lt;span class="n"&gt;sol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSolanaAddress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;              &lt;span class="c1"&gt;// m/44'/501'/0'/0'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bitcoin covers legacy, nested SegWit, native SegWit and Taproot, on the main network and on every test network. Sending ether is an EIP-1559 transaction with the fees the node suggests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;EvmRpc&lt;/span&gt; &lt;span class="n"&gt;rpc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EvmRpc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NetworkTransport&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"https://ethereum-sepolia-rpc.publicnode.com"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;DerivationPath&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Wallet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;evmPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;EvmAddress&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEvmAddress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTransactionCount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"pending"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;UInt256&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;fees&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;suggestFees&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;                     &lt;span class="c1"&gt;// {tip, fee cap}&lt;/span&gt;
&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;gas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;estimateGas&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;EvmTransaction&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EvmTransaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;eip1559&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chainId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fees&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt; &lt;span class="n"&gt;fees&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;gas&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendRawTransaction&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;signEvmTransaction&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bitcoin goes through a PSBT, so the same code works with a hardware signer that only sees the partially signed transaction. Taproot inputs are signed with BIP-340 Schnorr automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;EsploraClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Utxo&lt;/span&gt; &lt;span class="n"&gt;coin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;esplora&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUtxos&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;BitcoinTransaction&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BitcoinTransaction&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addInput&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTxid&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;coin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getVout&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addOutput&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getValue&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;feeSats&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BitcoinAddress&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="nc"&gt;Psbt&lt;/span&gt; &lt;span class="n"&gt;psbt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Psbt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromTransaction&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;psbt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setWitnessUtxo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toOutput&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="n"&gt;psbt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;signAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBitcoinKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;psbt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;finalizeAll&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;txid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;esplora&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;broadcast&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;psbt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;extract&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keys are handled the way a security review expects. &lt;code&gt;VaultSigner&lt;/code&gt; keeps the phrase sealed behind biometrics or a password and refuses to sign while it's locked. &lt;code&gt;revealMnemonic&lt;/code&gt; returns a &lt;code&gt;char[]&lt;/code&gt; you can wipe, not a &lt;code&gt;String&lt;/code&gt; that lingers on the heap, and &lt;code&gt;SeedSigner.destroy()&lt;/code&gt; wipes the key.&lt;/p&gt;

&lt;h3&gt;
  
  
  An exchange
&lt;/h3&gt;

&lt;p&gt;An exchange is mostly the server side, and that's what Bastion's backend shows. It derives a deposit address per customer (&lt;code&gt;m/44'/60'/0'/0/n&lt;/code&gt;), scans new blocks, credits deposits once they're deep enough, and sweeps them to a hot wallet on a separate account. It stops scanning and writes an audit event on a reorganization deeper than its confirmation depth. Before every withdrawal it checks that reserves cover what customers hold, and withdrawals go only to addresses the customer allowed in advance. Order prices are collared against the index price, so a fat finger can't sell bitcoin for a dollar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBEW0N1c3RvbWVyIGRlcG9zaXQgYWRkcmVzc10gLS0-IFNbQmxvY2sgc2Nhbm5lcl0KICAgIFMgLS0-fGNvbmZpcm1lZHwgQ1tDcmVkaXQgdGhlIGxlZGdlcl0KICAgIEMgLS0-IEhbU3dlZXAgdG8gdGhlIGhvdCB3YWxsZXRdCiAgICBXW1dpdGhkcmF3YWwgcmVxdWVzdF0gLS0-IExbTGltaXRzLCBhbGxvdyBsaXN0LCBzdGVwIHVwXQogICAgTCAtLT4gUntSZXNlcnZlcyBjb3ZlciBpdD99CiAgICBSIC0tPnx5ZXN8IFRbU2lnbiwgd2lwZSB0aGUga2V5LCBicm9hZGNhc3RdCiAgICBSIC0tPnxub3wgQVtBdWRpdCBldmVudCwgcmVmdXNlZF0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBEW0N1c3RvbWVyIGRlcG9zaXQgYWRkcmVzc10gLS0-IFNbQmxvY2sgc2Nhbm5lcl0KICAgIFMgLS0-fGNvbmZpcm1lZHwgQ1tDcmVkaXQgdGhlIGxlZGdlcl0KICAgIEMgLS0-IEhbU3dlZXAgdG8gdGhlIGhvdCB3YWxsZXRdCiAgICBXW1dpdGhkcmF3YWwgcmVxdWVzdF0gLS0-IExbTGltaXRzLCBhbGxvdyBsaXN0LCBzdGVwIHVwXQogICAgTCAtLT4gUntSZXNlcnZlcyBjb3ZlciBpdD99CiAgICBSIC0tPnx5ZXN8IFRbU2lnbiwgd2lwZSB0aGUga2V5LCBicm9hZGNhc3RdCiAgICBSIC0tPnxub3wgQVtBdWRpdCBldmVudCwgcmVmdXNlZF0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="982" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every one of those steps lands in the double entry ledger and the hash chained audit log from the previous section, which is what lets the auditor trace a coin from deposit to withdrawal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;p&gt;The expensive parts (secp256k1, Schnorr, Ed25519, the hashes and PBKDF2) run as native C on iOS, macOS, Android, Windows and Linux, with a portable Java engine as the fallback. CI benchmarks every kernel against libsecp256k1, OpenSSL and BouncyCastle on the same machine, and a pull request that makes one slower fails.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fcrypto-bench-ratios.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fcrypto-bench-ratios.png" alt="Codename One throughput as a multiple of the reference library's, on the JVM, native and Android" width="799" height="548"&gt;&lt;/a&gt;Throughput as a multiple of the reference library. Above 1 is faster.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Natively, ECDSA signing matches libsecp256k1 (60,770 against 59,453 signatures a second), and Ed25519 signs 2.44 times as fast as OpenSSL. Even the portable Java engine, which the simulator uses, beats BouncyCastle on the JVM: 1.76 times on public key derivation, 1.49 on ECDSA signing, and 20 times the JDK's own Ed25519 provider. On Android the native engine verifies ECDSA signatures six times as fast as BouncyCastle on ART:&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fcrypto-bench-android.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fcrypto-bench-android.png" alt="Both Android engines as a multiple of BouncyCastle on ART" width="800" height="476"&gt;&lt;/a&gt;Android: the native and Java engines against BouncyCastle on ART.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;A phone signs one transaction at a time. An exchange or an indexer checks thousands of signatures at once, so &lt;code&gt;CryptoBatch&lt;/code&gt; takes a whole batch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CryptoBatch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;verifySecp256k1&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hashes&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signatures&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;publicKeys&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It spreads the batch across every core, and for large batches the GPU too: Metal on Apple, Vulkan elsewhere and WebGPU in the browser, split by measured speed. The GPU never sees a private key. On every core, a batch of 1,024 verifies ECDSA 7.46 times as fast as libsecp256k1 called once per signature, and Ed25519 15.6 times as fast. In the browser, WebGPU verifies ECDSA 8.5 times as fast as the portable engine.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fcrypto-bench-multicore.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fcrypto-bench-multicore.png" alt="A native batch on every core as a multiple of the reference library called once per entry" width="800" height="262"&gt;&lt;/a&gt;A batch on every core, against the reference library one call at a time.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fcrypto-bench-gpu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fcrypto-bench-gpu.png" alt="Signatures verified per second by the CPU, the GPU and both together" width="799" height="325"&gt;&lt;/a&gt;CPU, GPU and both together on an M4 Max.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;The GPU's gain on a machine with 12 fast cores is modest, up to 1.63 times for Ed25519, and the chapter says so. Where it pays is a smaller CPU and the browser. Every table and chart is in the &lt;a href="https://www.codenameone.com/developer-guide/crypto-wallets/" rel="noopener noreferrer"&gt;crypto wallets chapter&lt;/a&gt;, with the machine, the library versions and how to rerun them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's simulated
&lt;/h2&gt;

&lt;p&gt;Bastion is a reference, not a licensed bank. Deposits, card spending and the market (a random walk) are simulated, and Android integrity checks trust every device in the dev profile. There's no KYC or sanctions screening, no payment rail, no HSM and no regulatory reporting. The &lt;a href="https://www.codenameone.com/developer-guide/bastion-implementors-guide/" rel="noopener noreferrer"&gt;implementor's guide&lt;/a&gt; has a section for each of those that says what a production deployment needs. On the crypto side, ether can settle on a real Ethereum node, while bitcoin stays a ledger balance in the sample. Only the three chain families above are supported, and the node clients are blocking calls, so keep them off the UI thread.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Generate Bastion from the &lt;a href="https://www.codenameone.com/initializr/" rel="noopener noreferrer"&gt;Initializr&lt;/a&gt;, or run it from the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CN1_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev backend/server.sh run
mvn verify &lt;span class="nt"&gt;-Psimulator&lt;/span&gt; &lt;span class="nt"&gt;-DskipTests&lt;/span&gt; &lt;span class="nt"&gt;-Dcodename1&lt;/span&gt;.platform&lt;span class="o"&gt;=&lt;/span&gt;javase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sign in as &lt;code&gt;customer@bastion.example&lt;/code&gt; with the password &lt;code&gt;bastion-demo-password&lt;/code&gt;, then open the Security Center and see what your build actually protects.&lt;/p&gt;

&lt;p&gt;Tomorrow: &lt;a href="https://www.codenameone.com/blog/reel-streaming-and-courses-template/" rel="noopener noreferrer"&gt;Reel: Your Own Streaming Service or Course Platform&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Your Swing and JavaFX Code on iPhone, Android and the Web</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Sun, 08 Nov 2026 14:01:01 +0000</pubDate>
      <link>https://dev.to/codenameone/your-swing-and-javafx-code-on-iphone-android-and-the-web-24c8</link>
      <guid>https://dev.to/codenameone/your-swing-and-javafx-code-on-iphone-android-and-the-web-24c8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fitrh411b66ao41hk4qw7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fitrh411b66ao41hk4qw7.jpg" alt="Swing and JavaFX code running as a native app" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's a lot of Swing and JavaFX code in the world. Much of it is internal tooling and line of business software that works fine and that nobody wants to rewrite. Then someone asks for it on an iPad, or in a browser, and the answer has always been "rewrite it."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; 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 &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Codename One can now build a large share of that code for iOS, Android, the web and native desktop, as a native app with no JVM inside. SwingSet2 and Notepad, the JDK's own demos, both build and run. This post shows what carries over, what it costs in size and start-up (less than the JVM), and why the parts that don't carry over have better replacements in a client app.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/developer-guide/img/desktop-compat-swingset2.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="1133" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-swingset2.png" alt="SwingSet2, the JDK Swing demo, running as a Codename One app on an iPad"&gt;&lt;/a&gt;SwingSet2, the JDK demo, on an iPad.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/desktop-compat-fx2048.png" rel="noopener noreferrer"&gt;&lt;img width="642" height="1300" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-fx2048.png" alt="The open source JavaFX 2048 game running as a Codename One app on an Android phone"&gt;&lt;/a&gt;The JavaFX 2048 game on a phone.&lt;p&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;You drop the project's &lt;code&gt;src/main&lt;/code&gt; into a Codename One project, or import it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn cn1:import-desktop-project &lt;span class="nt"&gt;-Dcn1&lt;/span&gt;.desktop.import&lt;span class="o"&gt;=&lt;/span&gt;/path/to/MyDesktopApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your code keeps compiling against the JDK's own Swing, or against our JavaFX API under the real &lt;code&gt;javafx.*&lt;/code&gt; names. A remap step after javac points the bytecode at our implementations of &lt;code&gt;java.awt&lt;/code&gt;, &lt;code&gt;javax.swing&lt;/code&gt;, &lt;code&gt;java.beans&lt;/code&gt;, &lt;code&gt;javax.imageio&lt;/code&gt;, a SwingX subset and a JavaFX subset, all written on top of Codename One. FXML files compile to Java classes at build time, and JavaFX CSS compiles to a table. Nothing parses XML on the device.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBTW1lvdXIgU3dpbmcgLyBKYXZhRlggc291cmNlXSAtLT4gSltqYXZhYyBhZ2FpbnN0IHRoZSB1c3VhbCBBUElzXQogICAgSiAtLT4gUltyZW1hcDogamF2YS5hd3QsIGphdmF4LnN3aW5nLCBqYXZhZnggdG8gQ29kZW5hbWUgT25lXQogICAgRlsuZnhtbCBhbmQgLmNzc10gLS0-IFhbY29tcGlsZWQgYXQgYnVpbGQgdGltZV0KICAgIFIgLS0-IENbY29tcGxpYW5jZSBjaGVjazogdW5zdXBwb3J0ZWQgQVBJIGlzIGEgYnVpbGQgZXJyb3JdCiAgICBYIC0tPiBDCiAgICBDIC0tPiBQW1BhcnBhclZNIG5hdGl2ZSBidWlsZHNd%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBTW1lvdXIgU3dpbmcgLyBKYXZhRlggc291cmNlXSAtLT4gSltqYXZhYyBhZ2FpbnN0IHRoZSB1c3VhbCBBUElzXQogICAgSiAtLT4gUltyZW1hcDogamF2YS5hd3QsIGphdmF4LnN3aW5nLCBqYXZhZnggdG8gQ29kZW5hbWUgT25lXQogICAgRlsuZnhtbCBhbmQgLmNzc10gLS0-IFhbY29tcGlsZWQgYXQgYnVpbGQgdGltZV0KICAgIFIgLS0-IENbY29tcGxpYW5jZSBjaGVjazogdW5zdXBwb3J0ZWQgQVBJIGlzIGEgYnVpbGQgZXJyb3JdCiAgICBYIC0tPiBDCiAgICBDIC0tPiBQW1BhcnBhclZNIG5hdGl2ZSBidWlsZHNd%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="936" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/developer-guide/img/desktop-compat-ios-swing-controls.png" rel="noopener noreferrer"&gt;&lt;img width="589" height="1278" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-ios-swing-controls.png" alt="Swing controls running on an iPhone"&gt;&lt;/a&gt;Swing controls.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/desktop-compat-ios-swing-tables.png" rel="noopener noreferrer"&gt;&lt;img width="589" height="1278" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-ios-swing-tables.png" alt="A JTable and a JXTable running on an iPhone"&gt;&lt;/a&gt;JTable and JXTable.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/desktop-compat-ios-javafx-controls.png" rel="noopener noreferrer"&gt;&lt;img width="589" height="1278" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-ios-javafx-controls.png" alt="JavaFX controls running on an iPhone"&gt;&lt;/a&gt;JavaFX controls.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/desktop-compat-ios-javafx-fxml.png" rel="noopener noreferrer"&gt;&lt;img width="589" height="1278" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-ios-javafx-fxml.png" alt="An FXML form styled with CSS running on an iPhone"&gt;&lt;/a&gt;FXML with its CSS.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Unmodified desktop code on an iPhone, captured by our screenshot tests.&lt;/p&gt;

&lt;p&gt;Our test set includes two JDK demos, SwingSet2 and Notepad, along with a Swing gallery, a falling blocks game, a JavaFX gallery, an open source 2048 game, an address book and a Mandelbrot explorer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;On Linux, the same app as a &lt;code&gt;jlink&lt;/code&gt; image against a Codename One native build:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Application&lt;/th&gt;
&lt;th&gt;JVM image, zipped&lt;/th&gt;
&lt;th&gt;Native, zipped&lt;/th&gt;
&lt;th&gt;Smaller by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SwingSet2, the JDK demo&lt;/td&gt;
&lt;td&gt;34.1 MB&lt;/td&gt;
&lt;td&gt;8.2 MB&lt;/td&gt;
&lt;td&gt;4.2x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notepad, the JDK demo&lt;/td&gt;
&lt;td&gt;32.8 MB&lt;/td&gt;
&lt;td&gt;6.5 MB&lt;/td&gt;
&lt;td&gt;5.0x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JavaFX gallery&lt;/td&gt;
&lt;td&gt;39.1 MB&lt;/td&gt;
&lt;td&gt;7.1 MB&lt;/td&gt;
&lt;td&gt;5.5x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Address book (JavaFX)&lt;/td&gt;
&lt;td&gt;39.1 MB&lt;/td&gt;
&lt;td&gt;6.5 MB&lt;/td&gt;
&lt;td&gt;6.0x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-linux-size-zipped.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-linux-size-zipped.png" alt="Zipped download size on Linux: a jlink JVM image against the Codename One native build, per sample" width="800" height="748"&gt;&lt;/a&gt;Download size, zipped.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-linux-startup-cold.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-linux-startup-cold.png" alt="Cold start on Linux: each sample on the default JVM, a tuned JVM and the Codename One native build" width="800" height="965"&gt;&lt;/a&gt;Cold start on Linux.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;From process start to the first painted frame, the native build starts faster than the default JVM in all 8 apps, by 1.5x to 5.5x. Against a JVM tuned with the serial GC, C1 only and an AppCDS archive, it's still faster in all 8, by 1.1x to 3.6x. The closest is the falling blocks game, at 86 ms native against 93 ms tuned. The JavaFX gallery starts in 116 ms native against 637 ms on the default JVM. The same apps build for iOS at 25 to 29 MB and as 2.8 to 4.4 MB Android APKs.&lt;/p&gt;

&lt;p&gt;Memory is closer than you might expect. At idle the native Swing gallery holds 64.5 MB resident against 84.2 MB on a tuned JVM. After a scripted session of clicks and drags it's 90.8 MB against 86.0 MB.&lt;/p&gt;

&lt;h2&gt;
  
  
  What isn't supported
&lt;/h2&gt;

&lt;p&gt;This is the important part, so here it is without softening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No binary drop in.&lt;/strong&gt; A third party jar that was compiled against &lt;code&gt;java.awt&lt;/code&gt;, &lt;code&gt;javax.swing&lt;/code&gt; or &lt;code&gt;javafx&lt;/code&gt; fails the build. We don't relocate other people's jars. If the library ships source, you can bring the source.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No class loaders, &lt;code&gt;ServiceLoader&lt;/code&gt;, modules or OSGi.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No runtime reflection.&lt;/strong&gt; &lt;code&gt;java.lang.reflect&lt;/code&gt; and &lt;code&gt;Class.getMethod&lt;/code&gt; are build errors. &lt;code&gt;Class.forName&lt;/code&gt; by string doesn't work on Android or the web.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Java serialization, &lt;code&gt;HttpURLConnection&lt;/code&gt;, &lt;code&gt;java.net.http&lt;/code&gt; or raw sockets&lt;/strong&gt; through these layers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ProcessBuilder&lt;/code&gt; doesn't exist on a phone.&lt;/strong&gt; Check &lt;code&gt;ProcessBuilder.isSupported()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only a subset of the API.&lt;/strong&gt; Swing is big. When you call something we don't have, the build tells you exactly what and where:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;com/example/OrdersPanel#restyleTree()V -&amp;gt; javax.swing.JTree.getUI() is not
supported by the Codename One Swing compatibility layer
(com/example/OrdersPanel.class) at OrdersPanel.java:48
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That error is the point. You find out at build time, on your machine, with a file and line number. You don't find out from a user on an iPad.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why pay for what you don't need?
&lt;/h2&gt;

&lt;p&gt;Look at that list again and ask where those features show up in a typical desktop app. Reflection usually powers a binding framework, a serialization library or a dependency injector. Class loaders power plugins. Serialization powers a cache or a save file.&lt;/p&gt;

&lt;p&gt;Codename One does those jobs at build time instead. Binding a form to a model is generated code, with no reflection and no listener bookkeeping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Bindable&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginModel&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"userField"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BindAttr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TEXT&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Required&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;         &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;   &lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"rememberMe"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BindAttr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SELECTED&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;remember&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same goes for JSON and XML mapping, REST and GraphQL clients and the SQLite ORM. They're all generated by the build, so they're type checked, they survive dead code elimination, and they cost nothing at startup. Reflection is what makes a JVM app big and slow to start. Removing it is a large part of why the native build is 4 to 6 times smaller.&lt;/p&gt;

&lt;h2&gt;
  
  
  What desktop Java never gave you
&lt;/h2&gt;

&lt;p&gt;You don't have to wait for a phone to get something out of this. On the desktop, today, the same app picks up things Swing developers have fought for years to get, and that JavaFX offers only as its own aging WebKit fork:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A real browser.&lt;/strong&gt; &lt;a href="https://www.codenameone.com/developer-guide/working-with-javascript/" rel="noopener noreferrer"&gt;&lt;code&gt;BrowserComponent&lt;/code&gt;&lt;/a&gt; embeds the system's engine: WebKit on the Mac, WebView2 on Windows and WebKitGTK on Linux, with two way calls between Java and JavaScript. No bundled Chromium, no &lt;code&gt;JEditorPane&lt;/code&gt; rendering HTML 3.2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The camera.&lt;/strong&gt; &lt;a href="https://www.codenameone.com/developer-guide/miscellaneous-features/#_capture_photos_video_audio" rel="noopener noreferrer"&gt;&lt;code&gt;Capture&lt;/code&gt;&lt;/a&gt; takes photos and video from the webcam on macOS, Windows and Linux. In Swing that has meant a native library and a prayer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bluetooth LE.&lt;/strong&gt; The &lt;a href="https://www.codenameone.com/developer-guide/bluetooth/" rel="noopener noreferrer"&gt;Bluetooth API&lt;/a&gt; talks to real devices on all three desktops, so the tool that configures your hardware no longer needs a vendor SDK or a serial port bridge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maps.&lt;/strong&gt; A &lt;a href="https://www.codenameone.com/developer-guide/maps/" rel="noopener noreferrer"&gt;vector &lt;code&gt;MapView&lt;/code&gt;&lt;/a&gt; drawn by Codename One itself, with markers and shapes, the same on the desktop as on a phone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The web.&lt;/strong&gt; The same app builds for the browser, so the internal tool people install today can also be a link, with nothing installed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generated network clients.&lt;/strong&gt; Point &lt;a href="https://www.codenameone.com/developer-guide/maven-appendix-goals/#_generate_openapi_client_generate_openapi" rel="noopener noreferrer"&gt;&lt;code&gt;cn1:generate-openapi&lt;/code&gt;&lt;/a&gt; at an OpenAPI 3 spec and you get a typed client. GraphQL and gRPC have goals of their own, and &lt;a href="https://www.codenameone.com/developer-guide/authentication-and-identity/" rel="noopener noreferrer"&gt;OAuth and sign in with Google or Apple&lt;/a&gt; are built in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite with an ORM&lt;/strong&gt; whose mapping mistakes fail the build, &lt;strong&gt;CSS styling&lt;/strong&gt; with light and dark themes and live reload, and &lt;strong&gt;a Java backend&lt;/strong&gt; in the same project, sharing your types.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And when the app does reach a phone, &lt;a href="https://www.codenameone.com/developer-guide/push-notifications/" rel="noopener noreferrer"&gt;push&lt;/a&gt;, &lt;a href="https://www.codenameone.com/developer-guide/biometric-authentication/" rel="noopener noreferrer"&gt;biometrics&lt;/a&gt;, &lt;a href="https://www.codenameone.com/developer-guide/near-field-communication/" rel="noopener noreferrer"&gt;NFC&lt;/a&gt; and &lt;a href="https://www.codenameone.com/developer-guide/miscellaneous-features/#_location_gps" rel="noopener noreferrer"&gt;location&lt;/a&gt; are there too.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SwingInterop.asComponent()&lt;/code&gt; and &lt;code&gt;FxInterop.asComponent()&lt;/code&gt; wrap a Swing component or a JavaFX node as a Codename One component, so you can migrate one panel at a time and put a Codename One map or camera view next to your existing table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.codenameone.com/developer-guide/desktop-interop/" rel="noopener noreferrer"&gt;desktop compatibility chapter&lt;/a&gt; has the full list of what's covered, the import steps and the benchmark method. Start with the screen your users ask for on mobile, run the import and read the build errors. They'll tell you how far you are.&lt;/p&gt;

&lt;p&gt;Tomorrow: &lt;a href="https://www.codenameone.com/blog/bastion-hardened-bank-template/" rel="noopener noreferrer"&gt;Bastion: A Hardened Bank Template, and Crypto Wallets in Core&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Your Compose Code, on More Screens and Less CPU</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Sat, 07 Nov 2026 14:03:54 +0000</pubDate>
      <link>https://dev.to/codenameone/your-compose-code-on-more-screens-and-less-cpu-2o3e</link>
      <guid>https://dev.to/codenameone/your-compose-code-on-more-screens-and-less-cpu-2o3e</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb8784f90rnir71gu321o.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb8784f90rnir71gu321o.jpg" alt="A Compose schedule screen running on Compose Multiplatform and on Codename One" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If Codename One didn't exist, Kotlin Multiplatform is probably the platform I'd choose. I prefer Java, but Kotlin is a good language, and KMP was one of the strongest platforms we measured against in the &lt;a href="https://www.codenameone.com/blog/flutter-better-in-java/" rel="noopener noreferrer"&gt;Flutter post&lt;/a&gt; this week. Its first take on UI leaned on heavyweight native widgets, the way React Native does. The team learned from that, and Compose Multiplatform today is a good product built by people who care about performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; 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 &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So this post isn't about KMP being bad. KMP is good. This is also good, and in a few ways better. Your Compose code now builds as a Codename One app, so it reaches more targets and did less work per frame on our benchmark. You don't have to pick one forever: the same &lt;code&gt;commonMain&lt;/code&gt; builds both ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you keep
&lt;/h2&gt;

&lt;p&gt;There's no transpiler here. kotlinc compiles your Kotlin for the JVM with the Compose compiler plugin, exactly as it does today. The Compose runtime and &lt;code&gt;kotlinx.coroutines&lt;/code&gt; are the upstream jars. The UI layer (&lt;code&gt;androidx.compose.ui&lt;/code&gt;, &lt;code&gt;foundation&lt;/code&gt;, &lt;code&gt;material3&lt;/code&gt; and &lt;code&gt;animation&lt;/code&gt;) is our reimplementation, written over Codename One components and graphics. Then ParparVM compiles everything ahead of time to a native binary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBDW2NvbW1vbk1haW4gS290bGluICsgQ29tcG9zZV0gLS0-IEtba290bGluYyArIENvbXBvc2UgcGx1Z2luXQogICAgSyAtLT4gUltDb21wb3NlIHJ1bnRpbWUsIHVubW9kaWZpZWRdCiAgICBLIC0tPiBVW0NvbXBvc2UgVUkgbGF5ZXIsIENvZGVuYW1lIE9uZV0KICAgIFIgLS0-IFBbUGFycGFyVk0sIGFoZWFkIG9mIHRpbWVdCiAgICBVIC0tPiBQCiAgICBQIC0tPiBJW2lPU10KICAgIFAgLS0-IEFbQW5kcm9pZF0KICAgIFAgLS0-IERbbWFjT1MgLyBXaW5kb3dzIC8gTGludXgsIG5vIEpWTV0KICAgIFAgLS0-IFdbV2ViXQogICAgUCAtLT4gVFt3YXRjaE9TIC8gdHZPU10%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBDW2NvbW1vbk1haW4gS290bGluICsgQ29tcG9zZV0gLS0-IEtba290bGluYyArIENvbXBvc2UgcGx1Z2luXQogICAgSyAtLT4gUltDb21wb3NlIHJ1bnRpbWUsIHVubW9kaWZpZWRdCiAgICBLIC0tPiBVW0NvbXBvc2UgVUkgbGF5ZXIsIENvZGVuYW1lIE9uZV0KICAgIFIgLS0-IFBbUGFycGFyVk0sIGFoZWFkIG9mIHRpbWVdCiAgICBVIC0tPiBQCiAgICBQIC0tPiBJW2lPU10KICAgIFAgLS0-IEFbQW5kcm9pZF0KICAgIFAgLS0-IERbbWFjT1MgLyBXaW5kb3dzIC8gTGludXgsIG5vIEpWTV0KICAgIFAgLS0-IFdbV2ViXQogICAgUCAtLT4gVFt3YXRjaE9TIC8gdHZPU10%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="956" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Dispatchers.Main&lt;/code&gt; is the Codename One event dispatch thread, so your coroutines behave the way you expect. Measuring follows Compose's rules: the modifier chain is walked outermost first, under Compose constraints. &lt;code&gt;LazyColumn&lt;/code&gt; and &lt;code&gt;LazyRow&lt;/code&gt; compose only what's on screen.&lt;/p&gt;

&lt;p&gt;Bringing a KMP module over is one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn cn1:import-kmp-project &lt;span class="nt"&gt;-Dcn1&lt;/span&gt;.kmp.import&lt;span class="o"&gt;=&lt;/span&gt;/path/to/MyKmpApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It copies &lt;code&gt;commonMain&lt;/code&gt; and your JVM source set into &lt;code&gt;common/src/main/kmp&lt;/code&gt;, leaving out the file that opens a desktop window. A whole app is a &lt;code&gt;ComposeForm&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Lifecycle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;runApp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;ComposeForm&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;LayoutScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The numbers, and what they don't mean
&lt;/h2&gt;

&lt;p&gt;The benchmark is Keynote, a Compose conference schedule app, built three ways: Compose on Codename One, Compose Multiplatform, and the same app written in plain Java on Codename One. All three were release builds on an iPhone 17 Pro simulator with Xcode 27, five runs each.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/developer-guide/img/kmp-keynote-schedule.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="709" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fkmp-keynote-schedule.png" alt="The Keynote schedule rendered by Compose Multiplatform on the left and by Codename One on the right"&gt;&lt;/a&gt;Keynote: Compose Multiplatform on the left, Codename One on the right.&lt;p&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Compose on Codename One&lt;/th&gt;
&lt;th&gt;Java on Codename One&lt;/th&gt;
&lt;th&gt;Compose Multiplatform&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Executable, stripped&lt;/td&gt;
&lt;td&gt;16.8 MB&lt;/td&gt;
&lt;td&gt;8.9 MB&lt;/td&gt;
&lt;td&gt;22.9 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App bundle&lt;/td&gt;
&lt;td&gt;18.3 MB&lt;/td&gt;
&lt;td&gt;10.2 MB&lt;/td&gt;
&lt;td&gt;22.9 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold start&lt;/td&gt;
&lt;td&gt;961 ms&lt;/td&gt;
&lt;td&gt;805 ms&lt;/td&gt;
&lt;td&gt;1008 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warm start, median&lt;/td&gt;
&lt;td&gt;580 ms&lt;/td&gt;
&lt;td&gt;572 ms&lt;/td&gt;
&lt;td&gt;604 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recomposition on a day change, median&lt;/td&gt;
&lt;td&gt;4.58 ms&lt;/td&gt;
&lt;td&gt;1.49 ms&lt;/td&gt;
&lt;td&gt;19.24 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scroll work per frame, median&lt;/td&gt;
&lt;td&gt;4.04 ms&lt;/td&gt;
&lt;td&gt;1.76 ms&lt;/td&gt;
&lt;td&gt;17.03 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frame interval, median&lt;/td&gt;
&lt;td&gt;35.45 ms&lt;/td&gt;
&lt;td&gt;34.79 ms&lt;/td&gt;
&lt;td&gt;33.35 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory footprint&lt;/td&gt;
&lt;td&gt;47 MB&lt;/td&gt;
&lt;td&gt;47 MB&lt;/td&gt;
&lt;td&gt;48 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-10-30%2Fkmp-keynote.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-10-30%2Fkmp-keynote.png" alt="Bar charts of recomposition time, scroll work per frame, executable size and cold start for the three builds" width="800" height="383"&gt;&lt;/a&gt;Lower is better in every panel.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;All three apps hit the same frame rate, and Compose Multiplatform is slightly ahead there. On a fast machine, a simple screen keeps up either way. What differs is how much CPU each one burns to get there: about 4 ms a frame for Compose on Codename One against about 17 ms for Compose Multiplatform. On a phone that's battery life, heat, and headroom for the frame where your screen isn't simple.&lt;/p&gt;

&lt;p&gt;A few caveats, because they matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the simulator, not a phone. Scrolling is programmatic, not a finger fling.&lt;/li&gt;
&lt;li&gt;Compose Multiplatform's first frame marker fires before Skia draws, so the start-up figures lean in its favor.&lt;/li&gt;
&lt;li&gt;An empty UIKit app takes 640 to 750 ms to start in this setup, so most of every start-up figure is iOS itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why imperative Java is faster and smaller still
&lt;/h2&gt;

&lt;p&gt;Look at the middle column. The same screen, written imperatively in Java, handles a day change in 1.49 ms, a third of the Compose version on the same engine, and its executable is about half the size.&lt;/p&gt;

&lt;p&gt;The benchmark is tilted toward Compose, too. The Java version was written to mirror the Compose app screen for screen, so it rebinds cards the way a recomposition would, and carries some of that model's overhead. A Java screen written from scratch the Codename One way would do less.&lt;/p&gt;

&lt;p&gt;Declarative UI pays for its model by recomposing, diffing and remembering, which is work an imperative screen never does, because it changes the one label that changed. The usual argument is that the model buys you something back. Here is the same session card both ways. Compose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;SessionCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;favorite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onFavorite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onOpen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxWidth&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verticalAlignment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CenterVertically&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;84&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
            &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeLabel&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"  -  "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MaterialTheme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typography&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;labelMedium&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MaterialTheme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typography&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;titleMedium&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxLines&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speaker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;", "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speaker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MaterialTheme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typography&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bodySmall&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nc"&gt;FavoriteButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;favorite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onFavorite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codename One, with the styling in CSS where it belongs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SessionCard&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Label&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"SessionTime"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Label&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"SessionTitle"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Label&lt;/span&gt; &lt;span class="n"&gt;speaker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"SessionSpeaker"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="n"&gt;heart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Favorite"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Session&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nc"&gt;SessionCard&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Schedule&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BorderLayout&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"SessionCard"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BorderLayout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CENTER&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BoxLayout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;encloseY&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;speaker&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BorderLayout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;EAST&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;heart&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;heart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addActionListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toggleFavorite&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;timeLabel&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"  -  "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;room&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;speaker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;speaker&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;", "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;company&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put them side by side and the claimed advantages don't hold up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Readability.&lt;/strong&gt; Both are a box with three lines of text and a button. Neither is shorter in any way that matters. The Java version names its parts, so &lt;code&gt;title&lt;/code&gt; is a field you can find, not the second &lt;code&gt;Text&lt;/code&gt; call in a nested lambda.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testability.&lt;/strong&gt; Compose tests find nodes by test tags you add for the purpose. A Codename One test holds the component, calls &lt;code&gt;bind&lt;/code&gt;, and asserts on &lt;code&gt;title.getText()&lt;/code&gt;. The &lt;a href="https://www.codenameone.com/developer-guide/testing-with-junit/" rel="noopener noreferrer"&gt;test framework&lt;/a&gt; and the simulator's MCP tools drive both the same way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging.&lt;/strong&gt; This is where imperative code wins outright. Set a breakpoint in &lt;code&gt;bind&lt;/code&gt; and you see the one call that changed the text, with a stack that leads back to your code. In Compose, the same change runs through the snapshot system, the runtime that schedules recomposition, and generated code from the compiler plugin, and "why did this recompose?" is a question you need a dedicated tool to answer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compose is a fine way to write UI, and if your team already has it, bring it. But it isn't simpler than the imperative code it replaced, and on this screen it costs two to three times the CPU and twice the binary. Codename One lets you choose per screen. Keep Compose where you have it, and write the hot screen in Java:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;form&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Mixed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BorderLayout&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BorderLayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NORTH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Codename One"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BorderLayout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CENTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ComposeView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;ListScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ComposeView&lt;/code&gt; is an ordinary &lt;code&gt;Container&lt;/code&gt;, so a Compose screen and a Codename One component share a form, a theme and an event thread.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's different on our side
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More targets.&lt;/strong&gt; iOS, Android, the web, native macOS, Windows and Linux with no JVM, plus watchOS and tvOS. The Keynote benchmark itself hasn't been built for watchOS, tvOS, Windows or Linux yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Java too.&lt;/strong&gt; Your team can write Java and Kotlin in one project, and share code with a Java backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smaller.&lt;/strong&gt; 16.8 MB of executable against 22.9 MB, or 8.9 MB if the screen is in Java.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build errors instead of surprises.&lt;/strong&gt; A composable or modifier we haven't implemented fails the build with its original name and source line. It doesn't render wrong on a device.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What doesn't work yet
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;LazyColumn(reverseLayout = true)&lt;/code&gt; throws; it isn't implemented. &lt;code&gt;Box&lt;/code&gt;, &lt;code&gt;Row&lt;/code&gt; and &lt;code&gt;Column&lt;/code&gt; aren't inline functions here, as they are upstream, because a public inline function can't call our internal layout code. The Compose runtime's inline functions are JVM 11 bytecode, so the build targets 11. On Gradle, the plugin refuses to build until &lt;code&gt;org.jetbrains.kotlin.plugin.compose&lt;/code&gt; is applied, and it tells you the line to add.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.codenameone.com/developer-guide/kotlin-multiplatform-interop/" rel="noopener noreferrer"&gt;Kotlin Multiplatform chapter&lt;/a&gt; covers the import, mixing Compose with Codename One components, and the benchmark. The Keynote source and its harness are in &lt;code&gt;scripts/kmp-bench&lt;/code&gt; in the &lt;a href="https://github.com/codenameone/CodenameOne" rel="noopener noreferrer"&gt;repository&lt;/a&gt;. If you run KMP in production, I'd love to hear which composable you hit first that we don't have.&lt;/p&gt;

&lt;p&gt;Tomorrow: &lt;a href="https://www.codenameone.com/blog/swing-and-javafx-on-phones/" rel="noopener noreferrer"&gt;Your Swing and JavaFX Code on iPhone, Android and the Web&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Flutter Better in Java</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Fri, 06 Nov 2026 16:03:34 +0000</pubDate>
      <link>https://dev.to/codenameone/flutter-better-in-java-1e48</link>
      <guid>https://dev.to/codenameone/flutter-better-in-java-1e48</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsdnbxxkwggqs38m3ye1t.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsdnbxxkwggqs38m3ye1t.jpg" alt="The Flutter Gallery running in Flutter and in Codename One side by side" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I usually blog about a feature the week it lands. This one landed a few weeks ago and was in development for months before that: Codename One can now take a Flutter app's Dart source and build it as a Codename One app. The results are amazing. I held this post back because I know how explosive these claims are. I wanted the numbers to come from a CI job that runs on every commit instead of a benchmark I tuned once on my laptop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; 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 &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the meantime we also added support for Kotlin Multiplatform with Compose, for a big part of Swing and JavaFX, and for classic Android apps on iOS. More is coming. The part that still surprises me is how well Codename One does against the originals where we measured them. The same Flutter app, built from the same Dart files, starts faster, uses less memory and ships less code when Codename One builds it. That holds on Android, macOS, Windows and Linux, and the sizes hold on iOS and the web too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same Dart, two builds
&lt;/h2&gt;

&lt;p&gt;This is the Flutter Gallery, the Flutter team's own showcase app. On the left it runs as Flutter built it. On the right, Codename One built it from the same Dart source, with no edits.&lt;/p&gt;



&lt;a href="https://www.codenameone.com/blog/release-2026-10-30/flutter-vs-codenameone.mp4" rel="noopener noreferrer"&gt;Download the side by side video&lt;/a&gt;.


&lt;p&gt;Look closely and you'll see it isn't pixel identical. In the Reply inbox the avatars are circles on the Flutter side and rounded squares on ours. Text wraps a few characters apart in places. The translation isn't perfect, and the &lt;a href="https://www.codenameone.com/developer-guide/flutter-interop/" rel="noopener noreferrer"&gt;developer guide&lt;/a&gt; says so up front: Dart support is experimental, some callbacks and parameters are accepted and then ignored, and so far we have verified it against one large app. If you bring a Flutter app over, test every interaction your screens depend on.&lt;/p&gt;

&lt;p&gt;But the hard part works. Layout, navigation, theming, text input, lists and the Material and Cupertino widgets all render from the original source, and what you get is a real Codename One app, not a picture of one.&lt;/p&gt;

&lt;p&gt;Bringing a project over is one command in a Codename One project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn cn1:import-flutter-project &lt;span class="nt"&gt;-Dcn1&lt;/span&gt;.flutter.import&lt;span class="o"&gt;=&lt;/span&gt;/path/to/flutter_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It copies &lt;code&gt;lib&lt;/code&gt;, the assets and the fonts, adds the Flutter runtime to the build, writes the main class and tells you which of your pub dependencies are covered. You don't need the Dart or Flutter SDK to build the result. The transpiler is Java and runs inside the normal Maven build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming This Week
&lt;/h2&gt;

&lt;p&gt;Today is the Flutter story and the numbers behind it. Then one post a day:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Saturday, October 31:&lt;/strong&gt; &lt;a href="https://www.codenameone.com/blog/compose-on-more-screens/" rel="noopener noreferrer"&gt;Your Compose Code, on More Screens and Less CPU&lt;/a&gt;. Kotlin Multiplatform and Compose, running on Codename One, and why the Java version of the same screen is faster still.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sunday, November 1:&lt;/strong&gt; &lt;a href="https://www.codenameone.com/blog/swing-and-javafx-on-phones/" rel="noopener noreferrer"&gt;Your Swing and JavaFX Code on iPhone, Android and the Web&lt;/a&gt;. What carries over, what doesn't, and why you might not miss the parts that don't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monday, November 2:&lt;/strong&gt; &lt;a href="https://www.codenameone.com/blog/bastion-hardened-bank-template/" rel="noopener noreferrer"&gt;Bastion: A Hardened Bank Template, and Crypto Wallets in Core&lt;/a&gt;. A virtual bank and exchange with passkeys, signed requests and a double entry ledger, plus wallets for Ethereum, Bitcoin, and Solana.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tuesday, November 3:&lt;/strong&gt; &lt;a href="https://www.codenameone.com/blog/reel-streaming-and-courses-template/" rel="noopener noreferrer"&gt;Reel: Your Own Streaming Service or Course Platform&lt;/a&gt;. Community uploads, courses, DRM and moderation in one Java project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wednesday, November 4:&lt;/strong&gt; &lt;a href="https://www.codenameone.com/blog/android-apps-everywhere-even-without-google/" rel="noopener noreferrer"&gt;GrapheneOS, LineageOS, /e/OS and Others Are Now Officially Supported&lt;/a&gt;. Room, WorkManager and document pickers in the Android layer, and apps that run on GrapheneOS as they are.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thursday, November 5:&lt;/strong&gt; &lt;a href="https://www.codenameone.com/blog/backend-caches-certificates-and-a-simulator-that-types/" rel="noopener noreferrer"&gt;Caches, Certificates, Streaming Uploads and a Simulator That Types&lt;/a&gt;. Eight backend upgrades and a simulator with a virtual keyboard and video recording.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/blog/compose-on-more-screens/" rel="noopener noreferrer"&gt;&lt;img width="800" height="709" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fkmp-keynote-schedule.png" alt="A Compose schedule app rendered by Compose Multiplatform and by Codename One"&gt;&lt;/a&gt;Compose, on Codename One.&lt;br&gt;
&lt;a href="https://www.codenameone.com/blog/bastion-hardened-bank-template/" rel="noopener noreferrer"&gt;&lt;img width="420" height="860" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fbastion-security-center.png" alt="The Bastion Security Center screen"&gt;&lt;/a&gt;Bastion's Security Center.&lt;br&gt;
&lt;a href="https://www.codenameone.com/blog/reel-streaming-and-courses-template/" rel="noopener noreferrer"&gt;&lt;img width="800" height="1422" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Freel-viewer-home.png" alt="The Reel home screen"&gt;&lt;/a&gt;Reel's catalog.&lt;br&gt;
&lt;a href="https://www.codenameone.com/blog/android-apps-everywhere-even-without-google/" rel="noopener noreferrer"&gt;&lt;img width="736" height="800" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fandroid-interop-material.png" alt="An Android Material screen rendered by Android and by Codename One"&gt;&lt;/a&gt;Android code, on every target.&lt;p&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;Every commit to Codename One builds the Flutter Gallery twice: once with Flutter's own release build and once with Codename One. CI measures both on the same machine in interleaved rounds, so a runner that slows down halfway through penalizes both builds equally. The figures below are from the run on October 29.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-10-30%2Fflutter-cold-start.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="383" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-10-30%2Fflutter-cold-start.png" alt="Cold start to first frame. Android: 395 ms for Codename One against 1247 ms for Flutter. macOS: 609 against 722 ms. Windows: 126 against 205 ms. Linux: 123 against 198 ms."&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-10-30%2Fflutter-memory.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="383" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-10-30%2Fflutter-memory.png" alt="Memory at rest. Android: 46 MB for Codename One against 79 MB for Flutter. macOS: 37 against 56 MB. Windows: 58 against 92 MB. Linux: 82 against 224 MB."&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-10-30%2Fflutter-code-size.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="518" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-10-30%2Fflutter-code-size.png" alt="Executable code. Android: 4.7 MB for Codename One against 20.6 MB for Flutter. iOS: 17.3 against 19.1 MB. macOS: 33.2 against 47.9 MB. Windows: 11.4 against 14.1 MB. Linux: 14.2 against 16.1 MB. Web: 12.9 against 25.2 MB."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CI collects 26 metrics: installed size, executable code and download size on six platforms, plus cold start and memory at rest on the four where we can measure both sides fairly. Codename One is ahead on all 26. The Android timings come from an emulator, and iOS start-up and memory aren't measured; both are explained below. The biggest gaps are on Android, where the app starts in a third of the time and carries a quarter of the code, and on Linux, where it settles at 82 MB against 224 MB.&lt;/p&gt;

&lt;p&gt;The comparison is tilted toward Flutter, not away from it. The app is Flutter's own showcase, written by the Flutter team in Flutter's idioms, and Codename One runs it as it is. To do that, Codename One carries a runtime that reimplements Flutter's widget, element and layout model on top of Codename One components. That layer is slower than Codename One code written directly; the &lt;a href="https://www.codenameone.com/blog/compose-on-more-screens/" rel="noopener noreferrer"&gt;Compose post&lt;/a&gt; shows the same effect, where the imperative Java version of a screen does a third of the work of the declarative one. A Codename One app never pays for that layer. We win while paying for it.&lt;/p&gt;

&lt;p&gt;CI also runs eleven compute workloads (hash maps, recursion, string building, sorting, arithmetic and more) in Java and in Dart. On every native platform we measured, Codename One is faster overall, by 1.3x on Android up to 1.9x on Windows and Linux:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-10-30%2Fflutter-compute.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="604" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-10-30%2Fflutter-compute.png" alt="Compute speedups for eleven workloads on Android, macOS, Windows and Linux. Codename One is faster or tied in 38 of the 43 measured cells. Flutter wins object allocation on Android (2.3x) and macOS (3.1x), value escape on Android (1.2x), and quicksort on macOS and long arithmetic on Windows by 2% and 6%. Geometric means: Android 1.3x, macOS 1.5x, Windows 1.9x, Linux 1.9x for Codename One."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Flutter takes 5 of the 43 cells. Two are within 6%. The real one is object allocation on Android and macOS, where Dart's allocator is quick at short lived objects; that's on our list.&lt;/p&gt;

&lt;p&gt;The web is the exception, and it's the one place Flutter is faster. We leave it out of the comparison because Flutter's web build gets some of the workloads wrong: integer arithmetic, random array access and quicksort return different checksums than the same code on every other platform, because Dart on the web runs numbers as JavaScript doubles. A benchmark that gets the answer wrong can't be timed against one that gets it right. On the workloads where both sides agree, Flutter is ahead. The browser is asynchronous by nature, and that's the one environment where Flutter's model helps it rather than costing it. The sizes still favor us there.&lt;/p&gt;

&lt;p&gt;Some things aren't measured, and the charts leave them out rather than guess. Flutter can't compile Dart ahead of time for the iOS simulator, so iOS start-up and memory would compare Flutter's debug engine against our release build. We report iOS sizes only. Web start-up needs a browser driver the benchmark doesn't have yet. Android timings come from an emulator, so they compare two builds on one machine rather than predicting a phone. The method, the harness and the raw numbers are in the &lt;a href="https://www.codenameone.com/developer-guide/flutter-interop/" rel="noopener noreferrer"&gt;Flutter chapter of the developer guide&lt;/a&gt;, and the benchmark lives in &lt;code&gt;scripts/flutter-bench&lt;/code&gt; in the &lt;a href="https://github.com/codenameone/CodenameOne" rel="noopener noreferrer"&gt;repository&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  How a compatibility layer can be faster than the original
&lt;/h2&gt;

&lt;p&gt;Each of these compatibility layers is a tool that runs at build time. None of them is a VM inside your app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBEW0ZsdXR0ZXIgLyBEYXJ0XSAtLT58dHJhbnNwaWxlZCB0byBKYXZhfCBKCiAgICBLW0tvdGxpbiBNdWx0aXBsYXRmb3JtIC8gQ29tcG9zZV0gLS0-fGtvdGxpbmMgKyBDb21wb3NlIFVJIGxheWVyfCBKCiAgICBTW1N3aW5nIC8gSmF2YUZYXSAtLT58Ynl0ZWNvZGUgcmVtYXBwZWR8IEoKICAgIEFbQW5kcm9pZCBhcHBdIC0tPnxyZXNvdXJjZXMgY29tcGlsZWQsIGJ5dGVjb2RlIHJlbWFwcGVkfCBKCiAgICBKW0phdmEgYnl0ZWNvZGUgKyBDb2RlbmFtZSBPbmUgY29yZV0gLS0-IFBbUGFycGFyVk0gLyBuYXRpdmUgcG9ydHNdCiAgICBQIC0tPiBJW2lPU10KICAgIFAgLS0-IE5bQW5kcm9pZF0KICAgIFAgLS0-IE1bbWFjT1MgLyBXaW5kb3dzIC8gTGludXhdCiAgICBQIC0tPiBXW1dlYl0KICAgIFAgLS0-IE9bd2F0Y2hPUyAvIHR2T1Nd%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgICBEW0ZsdXR0ZXIgLyBEYXJ0XSAtLT58dHJhbnNwaWxlZCB0byBKYXZhfCBKCiAgICBLW0tvdGxpbiBNdWx0aXBsYXRmb3JtIC8gQ29tcG9zZV0gLS0-fGtvdGxpbmMgKyBDb21wb3NlIFVJIGxheWVyfCBKCiAgICBTW1N3aW5nIC8gSmF2YUZYXSAtLT58Ynl0ZWNvZGUgcmVtYXBwZWR8IEoKICAgIEFbQW5kcm9pZCBhcHBdIC0tPnxyZXNvdXJjZXMgY29tcGlsZWQsIGJ5dGVjb2RlIHJlbWFwcGVkfCBKCiAgICBKW0phdmEgYnl0ZWNvZGUgKyBDb2RlbmFtZSBPbmUgY29yZV0gLS0-IFBbUGFycGFyVk0gLyBuYXRpdmUgcG9ydHNdCiAgICBQIC0tPiBJW2lPU10KICAgIFAgLS0-IE5bQW5kcm9pZF0KICAgIFAgLS0-IE1bbWFjT1MgLyBXaW5kb3dzIC8gTGludXhdCiAgICBQIC0tPiBXW1dlYl0KICAgIFAgLS0-IE9bd2F0Y2hPUyAvIHR2T1Nd%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1024" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Dart transpiler reads your widget source during &lt;code&gt;generate-sources&lt;/code&gt; and writes Java. A runtime library then implements Flutter's layout and composition model (constraints down, sizes up, elements reconciled against widgets) on top of Codename One containers. It does not reimplement Flutter's rasterizer. Painting is ours. Compose works the same way: kotlinc compiles your Kotlin, the Compose runtime ships unmodified, and only the UI layer is ours. Swing, JavaFX and Android apps compile against their usual APIs, and a remap step points the bytecode at our implementations.&lt;/p&gt;

&lt;p&gt;So the overhead is real, and it's added on top of Codename One. The reason the result still wins is what's underneath. ParparVM compiles Java ahead of time to C and then to a native binary, and its dead code elimination drops whatever your app doesn't reach. There's no Dart VM, no Skia or Impeller engine and no second runtime in the bundle. That's how the Android build ends up with 4.7 MB of executable code against 20.6 MB.&lt;/p&gt;

&lt;p&gt;Here is the Dart that calls into the platform. It's ordinary Flutter code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;batteryChannel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MethodChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'com.example.app/battery'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getBatteryLevel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;batteryChannel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'getBatteryLevel'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is the other side of that channel, in plain Java, with no plugin and no per-platform Swift or Kotlin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;MethodChannel&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MethodChannel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.example.app/battery"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setMethodCallHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MethodChannel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MethodCallHandler&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onMethodCall&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MethodCall&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MethodChannel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Result&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"getBatteryLevel"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;87&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;notImplemented&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't have to move the whole app either. &lt;code&gt;FlutterUI.wrap()&lt;/code&gt; returns an ordinary &lt;code&gt;Container&lt;/code&gt;, so a Dart screen can sit inside a Codename One form next to components you wrote in Java:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Form&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Form&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Dashboard"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BoxLayout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Written in Codename One"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FlutterUI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;wrap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Rendered from Dart"&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Also Codename One"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;show&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Similar in spirit
&lt;/h2&gt;

&lt;p&gt;Flutter and Codename One made the same bet. Neither wraps the platform's widgets. Both draw every component themselves on a lightweight surface, so you control every pixel and the behavior doesn't drift between platforms. Codename One still ships native themes, so an app can look like iOS on an iPhone and like Material on Android, or look like your brand everywhere. That shared architecture is why a Flutter widget tree maps onto Codename One so naturally. The differences are in what sits around that core: the threading model, how apps are built and how features ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can't Flutter do the same?
&lt;/h2&gt;

&lt;p&gt;No. Flutter could, in theory, run a Codename One app the way we run a Flutter app. It would be slow, because the two models point in opposite directions.&lt;/p&gt;

&lt;p&gt;Flutter is asynchronous to the core. Dart runs in an event loop, platform calls are messages that come back as futures, and the engine draws on its own threads. Codename One is synchronous: your code calls the platform and gets an answer. Emulating a synchronous API on top of an asynchronous one means queuing, waiting and copying on every call. Going the other way is cheap: a future is a callback, and a callback is a method call.&lt;/p&gt;

&lt;p&gt;The same reason explains why Flutter can't catch Codename One on most platforms. Operating systems, graphics APIs and hardware are synchronous. Codename One calls them directly. Flutter goes through a message channel and its own engine to reach them.&lt;/p&gt;

&lt;p&gt;Android makes it obvious. The native platform on Android is Java. Codename One compiles to it and talks to Android the way Android expects. Flutter avoids it, shipping its own VM and engine beside the one the phone already has. That's why Android shows the biggest gap: a third of the start-up time and a quarter of the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two differences you feel every day
&lt;/h2&gt;

&lt;p&gt;The threading model shows up in the benchmarks. These two don't, but you notice them every day you work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Builders
&lt;/h3&gt;

&lt;p&gt;A Codename One app is a Java project. The build of a native app happens in a builder: our cloud build servers or a local build, which take your compiled code and produce the iOS, Android or desktop binary. That moves the hard part of the developer experience into our hands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build iOS apps from Windows or Linux.&lt;/strong&gt; The cloud builder holds the Mac, Xcode and the signing steps. You don't need to own a Mac to ship to the App Store.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cheap CI.&lt;/strong&gt; Your unit and UI tests run in Java, in the simulator, on any CI runner. No emulator to boot and no macOS runner to pay for on every commit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New features without migration work.&lt;/strong&gt; When Apple or Google change a requirement, or we add a capability, we update the builder. Your project picks it up on the next build, with nothing to upgrade or reconfigure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Everything in one place
&lt;/h3&gt;

&lt;p&gt;Codename One used to aim for a modular core, with features broken out into cn1libs. Flutter is going through that phase now, moving pieces such as its Material and Cupertino themes out of the framework into independent packages. We went the opposite way. The Codename One core now covers what Flutter does, plus most of what people reach for in Flutter's most popular packages, and this week's posts add more.&lt;/p&gt;

&lt;p&gt;A bigger core costs you nothing, because you don't pay for what you don't use, and that works on two levels. ParparVM drops every class your app doesn't reach. The builder goes further: it looks at what your compiled code calls and adds a native library only when a feature needs it. This week's crypto support is a good example. Its native kernels for secp256k1, Ed25519 and the hashes are what make it fast, and an app that never touches the crypto API shouldn't carry them. It doesn't. Call a wallet or a signature API and the builder links them in, and you get native speed that our CI measures against the reference libraries, libsecp256k1 and OpenSSL among them. That's how the builds above stay smaller than Flutter's. What you get instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One version.&lt;/strong&gt; No matrix of package versions to keep compatible, and no upgrade that breaks because two packages disagree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing abandoned.&lt;/strong&gt; No package your app depends on that its author stopped maintaining two years ago.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A short supply chain.&lt;/strong&gt; This is the big one. Every package in a dependency tree is a person you trust with your users' devices. A maintainer who burns out, sells the package, gets angry or gets hacked can push code into your app on the next update, and attacks on open source packages have done exactly that. With the features in core, your app's dependencies are one project with one review process, not a tree of packages from strangers. That fits the rest of this week: the &lt;a href="https://www.codenameone.com/blog/bastion-hardened-bank-template/" rel="noopener noreferrer"&gt;Bastion&lt;/a&gt; template is a hardened bank because security is something we take seriously, and banks and other regulated organizations build on Codename One. For them, a short supply chain is a requirement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything moves together.&lt;/strong&gt; When we fix rendering, add a platform or change the build, every feature comes along, including the niche ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One source of truth.&lt;/strong&gt; The developer guide documents all of it, and when something breaks there is one team responsible for the answer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  We can finally show the code
&lt;/h2&gt;

&lt;p&gt;For years, every Codename One against Flutter discussion went the same way. I'd explain why ahead of time compiled Java with a lightweight UI should produce a smaller, faster app. Someone would explain why it shouldn't. Neither of us had the same app built both ways, so it came down to which camp you were already in.&lt;/p&gt;

&lt;p&gt;Now there's one app, built both ways, measured on every commit. If you think a number is wrong, the harness is in the repository and you can rerun it.&lt;/p&gt;

&lt;p&gt;The part I didn't expect is how much this made Codename One better. Each compatibility layer exposed problems we had missed for years:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Styles were parsed over and over.&lt;/strong&gt; Every component re-parsed its pressed and disabled styles from the theme. Caching them took component creation in the Gallery from 131 ms to 17 ms and resident memory from 247 MB to 144 MB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every string constant was created at startup.&lt;/strong&gt; ParparVM materialized every string literal before &lt;code&gt;main()&lt;/code&gt; ran. Making the pool lazy cut that step from 119 ms to 46 ms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rounded images were copied.&lt;/strong&gt; Rounding an image's corners built a second bitmap, 32 times during the first frame. Doing it in the fragment shader removed the copies and stopped memory from swinging between runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Images decoded inside the first frame.&lt;/strong&gt; Deferring that decode took the first frame from 302 ms to 213 ms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An idle app burned CPU.&lt;/strong&gt; The Compose work found, and we fixed, a bug where on phones with a notch, safe area padding kept queuing layout so an idle app sat at 14% to 26% CPU.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these fixes are specific to Flutter. If you write Codename One apps in plain Java and never touch a Dart file, your app gets these fixes too: faster start-up, lower memory and an idle CPU that stays idle.&lt;/p&gt;

&lt;h2&gt;
  
  
  More This Week
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Your Compose code, on more screens
&lt;/h3&gt;

&lt;p&gt;Kotlin Multiplatform is a good platform, and Compose Multiplatform is well built. Codename One now runs the same Compose code on more targets, used about a quarter of the CPU per frame on our benchmark screen, and lets you drop to Java where it counts.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/developer-guide/img/kmp-keynote-schedule.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="709" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fkmp-keynote-schedule.png" alt="The same Compose schedule screen rendered by Compose Multiplatform and by Codename One"&gt;&lt;/a&gt;The same Compose screen: Compose Multiplatform on the left, Codename One on the right.&lt;p&gt;&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Lifecycle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;runApp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;ComposeForm&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;LayoutScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Saturday's &lt;a href="https://www.codenameone.com/blog/compose-on-more-screens/" rel="noopener noreferrer"&gt;Compose article&lt;/a&gt; has the numbers and their caveats.&lt;/p&gt;

&lt;h3&gt;
  
  
  Swing and JavaFX on phones
&lt;/h3&gt;

&lt;p&gt;A large share of real Swing and JavaFX code now builds for iOS, Android and the web: the samples we test include SwingSet2 and Notepad, the JDK's own demos. On Linux the native build is 4 to 6 times smaller zipped than the same app on the JVM, and starts faster in all 8 apps, even against a JVM tuned with AppCDS.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/developer-guide/img/desktop-compat-swingset2.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="1133" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-swingset2.png" alt="SwingSet2, the JDK Swing demo, running as a Codename One app on an iPad"&gt;&lt;/a&gt;SwingSet2, the JDK demo, on an iPad.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/desktop-compat-fx2048.png" rel="noopener noreferrer"&gt;&lt;img width="642" height="1300" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-fx2048.png" alt="The open source JavaFX 2048 game running as a Codename One app on an Android phone"&gt;&lt;/a&gt;The JavaFX 2048 game on a phone.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-ios-swing-controls.png" class="article-body-image-wrapper"&gt;&lt;img width="589" height="1278" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-ios-swing-controls.png" alt="Swing controls running on an iPhone"&gt;&lt;/a&gt;Swing controls.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-ios-swing-tables.png" class="article-body-image-wrapper"&gt;&lt;img width="589" height="1278" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-ios-swing-tables.png" alt="A JTable running on an iPhone"&gt;&lt;/a&gt;JTable.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-ios-javafx-controls.png" class="article-body-image-wrapper"&gt;&lt;img width="589" height="1278" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-ios-javafx-controls.png" alt="JavaFX controls running on an iPhone"&gt;&lt;/a&gt;JavaFX.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-ios-javafx-fxml.png" class="article-body-image-wrapper"&gt;&lt;img width="589" height="1278" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fdesktop-compat-ios-javafx-fxml.png" alt="An FXML form running on an iPhone"&gt;&lt;/a&gt;FXML and CSS.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Sunday's &lt;a href="https://www.codenameone.com/blog/swing-and-javafx-on-phones/" rel="noopener noreferrer"&gt;Swing and JavaFX article&lt;/a&gt; goes through what carries over and why you won't miss what doesn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  A bank you can study, and wallets in core
&lt;/h3&gt;

&lt;p&gt;A large share of our users are banks and financial institutions. &lt;strong&gt;Bastion&lt;/strong&gt; is a reference implementation for them: a virtual bank and small exchange with passkeys, TOTP, signed requests, device keys, a hash chained audit log, a double entry ledger and an auditor console. Codename One also gained wallet APIs for Ethereum, Bitcoin, and Solana.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fbastion-dashboard.png" class="article-body-image-wrapper"&gt;&lt;img width="420" height="860" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fbastion-dashboard.png" alt="The Bastion dashboard"&gt;&lt;/a&gt;Four currencies.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fbastion-order-book.png" class="article-body-image-wrapper"&gt;&lt;img width="420" height="860" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fbastion-order-book.png" alt="The BTC/USD market with candles and the order book"&gt;&lt;/a&gt;The BTC/USD market.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fbastion-security-center.png" class="article-body-image-wrapper"&gt;&lt;img width="420" height="860" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fbastion-security-center.png" alt="The Bastion Security Center"&gt;&lt;/a&gt;The Security Center.&lt;p&gt;&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Wallet&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Wallet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromMnemonic&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;EvmAddress&lt;/span&gt; &lt;span class="n"&gt;eth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEvmAddress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;BitcoinAddress&lt;/span&gt; &lt;span class="n"&gt;btc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBitcoinAddress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BitcoinNetwork&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MAINNET&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Wallet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BIP84&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;SolanaAddress&lt;/span&gt; &lt;span class="n"&gt;sol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSolanaAddress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monday's &lt;a href="https://www.codenameone.com/blog/bastion-hardened-bank-template/" rel="noopener noreferrer"&gt;Bastion article&lt;/a&gt; covers the hardening, one layer at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Streaming and courses
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Reel&lt;/strong&gt; is a streaming service and its server in one Java project. Switch a preset and it becomes a subscription service, a creator platform in the style of YouTube, or an academy with courses, lessons, and quizzes. &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Freel-viewer-home.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="1422" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Freel-viewer-home.png" alt="The Reel catalog"&gt;&lt;/a&gt;The catalog.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Freel-viewer-player-tracks.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="1422" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Freel-viewer-player-tracks.png" alt="The Reel player"&gt;&lt;/a&gt;The player.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Freel-viewer-course.png" class="article-body-image-wrapper"&gt;&lt;img width="800" height="1422" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Freel-viewer-course.png" alt="A Reel course"&gt;&lt;/a&gt;A course.&lt;p&gt;&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;StreamRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StreamRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manifestUrl&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDrm&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DrmConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DrmScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WIDEVINE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;licenseUrl&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tuesday's &lt;a href="https://www.codenameone.com/blog/reel-streaming-and-courses-template/" rel="noopener noreferrer"&gt;Reel article&lt;/a&gt; walks through uploads, moderation, and DRM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Android code everywhere, including GrapheneOS
&lt;/h3&gt;

&lt;p&gt;The Android compatibility layer now covers Room, WorkManager, location, sensors, foreground services and the document picker intents, all running on iOS too. And GrapheneOS, LineageOS, CalyxOS and /e/OS are now officially supported, with a boundary CI verifies: one APK checks for Play Services at run time, and push works through UnifiedPush.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fandroid-interop-material.png" class="article-body-image-wrapper"&gt;&lt;img width="736" height="800" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fandroid-interop-material.png" alt="A Material 3 screen on Android and on Codename One"&gt;&lt;/a&gt;Material 3, Android left, Codename One right.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fandroid-interop-recycler.png" class="article-body-image-wrapper"&gt;&lt;img width="736" height="800" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fandroid-interop-recycler.png" alt="A RecyclerView on Android and on Codename One"&gt;&lt;/a&gt;RecyclerView, same source.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Wednesday's &lt;a href="https://www.codenameone.com/blog/android-apps-everywhere-even-without-google/" rel="noopener noreferrer"&gt;Android article&lt;/a&gt; has the details.&lt;/p&gt;

&lt;h3&gt;
  
  
  A modern backend, and a simulator that types
&lt;/h3&gt;

The simulated keyboard sliding up over a sign up form.

&lt;p&gt;Thursday closes the week with eight backend upgrades (a bounded cache, outbound OAuth tokens, PKI, JSON codecs, streaming uploads, error advice, &lt;code&gt;java.time&lt;/code&gt; in the ORM and test slices) and a simulator that slides up a virtual keyboard and records video. See the &lt;a href="https://www.codenameone.com/blog/backend-caches-certificates-and-a-simulator-that-types/" rel="noopener noreferrer"&gt;backend and simulator article&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The universal platform
&lt;/h2&gt;

&lt;p&gt;Last week I made the case that &lt;a href="https://www.codenameone.com/blog/rewrite-everything-in-java-not-rust/" rel="noopener noreferrer"&gt;a Java word processor beats its Rust twin&lt;/a&gt;. This week the swing is bigger. Flutter, Compose, Swing, JavaFX and Android code all build into the same kind of app and run on iOS, Android, desktop, the web and the watch.&lt;/p&gt;

&lt;p&gt;Instead of coding to an operating system, you code to Codename One, and Codename One becomes the operating system. The usual price for that kind of abstraction is performance. We don't charge it. You get more performance, more features, more compatibility and more platforms, and it's free and open source.&lt;/p&gt;

&lt;p&gt;If you have a Flutter app, run the import on a copy and build it for the simulator. Tell us which screen breaks first; that's the most useful bug report we can get right now. If you're on Kotlin, Swing, JavaFX or Android, the rest of this week is for you. And if you think a number above is wrong, the benchmark is in the repository. Rerun it and open an issue.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>WebRTC Calls, Key Agreement and ORM Checks at Build Time</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Thu, 05 Nov 2026 21:17:48 +0000</pubDate>
      <link>https://dev.to/codenameone/webrtc-calls-key-agreement-and-orm-checks-at-build-time-2cgb</link>
      <guid>https://dev.to/codenameone/webrtc-calls-key-agreement-and-orm-checks-at-build-time-2cgb</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbs3bwev9ufdt7e2lcyy6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbs3bwev9ufdt7e2lcyy6.jpg" alt="WebRTC calls and compile-time checked backends in Codename One" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last post this week covers what &lt;a href="https://www.codenameone.com/blog/launch-your-own-private-messenger/" rel="noopener noreferrer"&gt;Parley&lt;/a&gt;, &lt;a href="https://www.codenameone.com/blog/launch-your-own-food-delivery-app/" rel="noopener noreferrer"&gt;Savor&lt;/a&gt; and &lt;a href="https://www.codenameone.com/blog/create-an-uber-clone-in-1-minute/" rel="noopener noreferrer"&gt;Wayline&lt;/a&gt; needed underneath: calls, encryption and a backend you can trust with real data. These are features you'd usually assemble from three vendors and a few weekends of glue code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; 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 &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Voice and video calls with WebRTC
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;com.codename1.webrtc&lt;/code&gt; carries live audio and video directly between two devices, encrypted on the way, with a TURN relay when there's no direct path. On Android it's Google's WebRTC library, on iOS the Apple build of the same library, and in the browser it's the browser's own WebRTC. Referencing the package is what adds the native library to a build, so apps that don't make calls don't pay for it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;WebRtc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;openUserMedia&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;ready&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;PeerConnectionConfig&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PeerConnectionConfig&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addIceServer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IceServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stun&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"stun:stun.example.com:3478"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;pc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WebRtc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createPeerConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PeerConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Listener&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;iceCandidate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PeerConnection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;IceCandidate&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;signalling&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;stateChanged&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PeerConnection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;remoteTrack&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PeerConnection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MediaTrack&lt;/span&gt; &lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isVideo&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BorderLayout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CENTER&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;VideoView&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;track&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
                &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;revalidate&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;
    &lt;span class="n"&gt;pc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addTrack&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAudio&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;pc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addTrack&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getVideo&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;pc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createOffer&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;ready&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offer&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLocalDescription&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offer&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ready&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;signalling&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offer&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Signaling is yours on purpose. The package creates the offer, the answer and the candidates, and you send them over whatever you already have: a WebSocket, your server, a push. So the same call code works with a Codename One backend or with anything else. Parley sends them over its live channel, and the media never touches its server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBBIGFzIENhbGxlcgogICAgcGFydGljaXBhbnQgUyBhcyBZb3VyIHNlcnZlcgogICAgcGFydGljaXBhbnQgQiBhcyBDYWxsZWUKICAgIEEtPj5TOiBPZmZlciBhbmQgY2FuZGlkYXRlcwogICAgUy0-PkI6IE9mZmVyIGFuZCBjYW5kaWRhdGVzCiAgICBCLT4-UzogQW5zd2VyIGFuZCBjYW5kaWRhdGVzCiAgICBTLT4-QTogQW5zd2VyIGFuZCBjYW5kaWRhdGVzCiAgICBBLS0-PkI6IEVuY3J5cHRlZCBhdWRpbyBhbmQgdmlkZW8sIHBlZXIgdG8gcGVlcg%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBBIGFzIENhbGxlcgogICAgcGFydGljaXBhbnQgUyBhcyBZb3VyIHNlcnZlcgogICAgcGFydGljaXBhbnQgQiBhcyBDYWxsZWUKICAgIEEtPj5TOiBPZmZlciBhbmQgY2FuZGlkYXRlcwogICAgUy0-PkI6IE9mZmVyIGFuZCBjYW5kaWRhdGVzCiAgICBCLT4-UzogQW5zd2VyIGFuZCBjYW5kaWRhdGVzCiAgICBTLT4-QTogQW5zd2VyIGFuZCBjYW5kaWRhdGVzCiAgICBBLS0-PkI6IEVuY3J5cHRlZCBhdWRpbyBhbmQgdmlkZW8sIHBlZXIgdG8gcGVlcg%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="686" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key agreement and HKDF everywhere
&lt;/h2&gt;

&lt;p&gt;End-to-end encryption needs two parties that have never met to agree on a key over a channel anyone can read. Codename One now does that on every port and on the backend. &lt;code&gt;KeyGenerator.ec(256)&lt;/code&gt; makes a P-256 key pair, &lt;code&gt;KeyAgreement.ecdh&lt;/code&gt; combines your private key with the other side's public key, and &lt;code&gt;Hkdf&lt;/code&gt; turns the shared secret into a proper key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KeyAgreement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ecdh&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPrivateKey&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;theirs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPublicKey&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// The shared secret is not a key yet. HKDF turns it into one, and the info&lt;/span&gt;
&lt;span class="c1"&gt;// label keeps keys derived for different purposes apart.&lt;/span&gt;
&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"chat message key v1"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBytes&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Hkdf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;derive&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SHA-256"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SecureRandom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;sealed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cipher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;aesEncrypt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Cipher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AES_GCM&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SecretKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AES"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key agreement runs in the platform's native crypto provider. &lt;code&gt;Hkdf&lt;/code&gt; is portable Java over &lt;code&gt;Hmac&lt;/code&gt;, so it gives the same bytes on every device and on your server. These are the building blocks of Parley's X3DH and Double Ratchet, and they're just as useful for encrypted backups, secure pairing or signing data between your app and your server.&lt;/p&gt;

&lt;h2&gt;
  
  
  A backend client for serious integrations
&lt;/h2&gt;

&lt;p&gt;Real backends call other services: a payment processor, a bank, a partner API. Many of those authenticate callers with a client certificate. The backend's &lt;code&gt;WebClient&lt;/code&gt; handles that, along with HTTP/2, timeouts and connection pooling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;partner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebClient&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setClientCertificate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ClientCertificate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pkcs12&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;archive&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRequireHttp2&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRequestTimeoutMillis&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server certificates and host names are always verified, and there is no option to turn that off. Two clients never share a connection, so a connection authenticated with one certificate is never used for a request made with another.&lt;/p&gt;

&lt;h2&gt;
  
  
  ORM mistakes fail the build
&lt;/h2&gt;

&lt;p&gt;This is my favorite one. With Hibernate and Spring, plenty of mapping mistakes compile fine and only blow up when the application starts, or worse, on the first request that touches the broken entity. You find out in staging if you're lucky.&lt;/p&gt;

&lt;p&gt;Codename One's ORM reads your entities when you compile, so those mistakes are build errors that name the class and the field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Temporal requires a java.util.Date field: signedUp
Persistent fields must be writable: name
@Entity class com.example.Customer must declare a public no-arg constructor
@Version requires an int or long field: revision
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repositories get the same treatment: an unsupported method signature, a derived query that names a field the entity doesn't have, or a missing parameter name stops the build and names the repository and the method.&lt;/p&gt;

&lt;p&gt;The same release makes it easier to put the ORM on a database you already have. Columns keep their names, native &lt;code&gt;TIMESTAMP&lt;/code&gt; and &lt;code&gt;DATE&lt;/code&gt; columns map to &lt;code&gt;java.util.Date&lt;/code&gt; with &lt;code&gt;@Temporal&lt;/code&gt;, and lifecycle callbacks can stay private:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"legacy_customer"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LegacyCustomer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt; &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"customer_no"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"full_name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Temporal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TemporalType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"signed_up"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt; &lt;span class="n"&gt;signedUp&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@PrePersist&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;defaults&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signedUp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;signedUp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before you deploy, &lt;code&gt;session.schemaReport()&lt;/code&gt; compares the mappings with the real tables and tells you what's missing, without changing anything in the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  That's the week
&lt;/h2&gt;

&lt;p&gt;Three new apps you can launch, a word processor that holds up against Rust, and a stack of features that make the next app easier. Everything is in the &lt;a href="https://github.com/codenameone/CodenameOne" rel="noopener noreferrer"&gt;repository&lt;/a&gt;, and the guides for &lt;a href="https://www.codenameone.com/developer-guide/webrtc/" rel="noopener noreferrer"&gt;WebRTC&lt;/a&gt;, &lt;a href="https://www.codenameone.com/developer-guide/security/" rel="noopener noreferrer"&gt;security&lt;/a&gt;, &lt;a href="https://www.codenameone.com/developer-guide/backend-web/" rel="noopener noreferrer"&gt;backend web&lt;/a&gt; and the &lt;a href="https://www.codenameone.com/developer-guide/annotation-sqlite-orm/" rel="noopener noreferrer"&gt;ORM&lt;/a&gt; have the details. If you build something with these, I want to see it.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Native Menus, File Dialogs and 3D Models</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Wed, 04 Nov 2026 20:25:15 +0000</pubDate>
      <link>https://dev.to/codenameone/native-menus-file-dialogs-and-3d-models-8pe</link>
      <guid>https://dev.to/codenameone/native-menus-file-dialogs-and-3d-models-8pe</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg7wbk1y8h4qfxlba59q0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg7wbk1y8h4qfxlba59q0.jpg" alt="A Codename One app with a native menu bar and file dialog" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A desktop app gets judged in the first ten seconds. If the menu bar is wrong, Cmd+Q does nothing or the Save dialog looks like it came from a different decade, people notice, even if they can't say what bothers them. The &lt;a href="https://www.codenameone.com/blog/rewrite-everything-in-java-not-rust/" rel="noopener noreferrer"&gt;word processor&lt;/a&gt; from last Friday's post needed all of that to hold up next to a native app, so those pieces are now part of Codename One. They work with the code you already write for a phone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; 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 &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real open and save dialogs
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;openFileChooser()&lt;/code&gt; picks one file. Desktop apps need more: several filters, a suggested name, extra options in the dialog and a preview while the user browses. &lt;code&gt;showOpenDialog()&lt;/code&gt; and &lt;code&gt;showSaveDialog()&lt;/code&gt; cover that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Display&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;showOpenDialog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenFileRequest&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Import photos"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;multiSelect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileTypeFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Images"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"png"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"jpg"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;mimeTypes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"image/*"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addOption&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FileDialogOption&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;checkbox&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"strip"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Remove location data"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;preview&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InputStream&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FileSystemStorage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;openInputStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ScaleImageLabel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createImage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No preview"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}),&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isCancelled&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;strip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getOptionValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"strip"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPaths&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// import each file&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/blog/release-2026-10-23/file-dialog-preview-option.jpg" rel="noopener noreferrer"&gt;&lt;img width="800" height="634" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-10-23%2Ffile-dialog-preview-option.jpg" alt="An open dialog with an image preview, an Images filter and a Remove location data checkbox"&gt;&lt;/a&gt;The request above in the simulator, with the preview and the option.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Every platform opens and saves files. The extras appear where the platform has them, and the rest ignore them, so one request works everywhere:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Open&lt;/th&gt;
&lt;th&gt;Save&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NSOpenPanel&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;NSSavePanel&lt;/code&gt;, with format list, options and tag field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IFileOpenDialog&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;IFileSaveDialog&lt;/code&gt;, with options as dialog controls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GtkFileChooser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GtkFileChooser&lt;/code&gt; with overwrite confirmation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android&lt;/td&gt;
&lt;td&gt;The system document picker, several files, folders&lt;/td&gt;
&lt;td&gt;Create document, with no storage permission&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iOS&lt;/td&gt;
&lt;td&gt;The document picker, several files, folders&lt;/td&gt;
&lt;td&gt;Exported through the document picker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser&lt;/td&gt;
&lt;td&gt;The browser's file picker&lt;/td&gt;
&lt;td&gt;A download with the suggested name&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A save request writes its content after the user has picked the location, off the EDT, and it can see which format and options were chosen. Platform hints, such as the macOS tag field or the folder Android starts in, are one call each.&lt;/p&gt;

&lt;h2&gt;
  
  
  A menu bar that follows each platform
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;Command&lt;/code&gt; already knows its name, icon, and action. Tell it where it goes and it shows up in the native menu bar on macOS, Windows, Linux and in the browser build. Paths make submenus, and check and radio items work the way you'd expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;zooms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"50%"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"100%"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"200%"&lt;/span&gt;&lt;span class="o"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;zoom&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;zooms&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Command&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zoom&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setZoom&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zoom&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDesktopMenu&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"View/Zoom"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;setDesktopMenuGroup&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;setDesktopRadioGroup&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"zoom"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDesktopChecked&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zoom&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getToolbar&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addCommandToOverflowMenu&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a phone the same commands stay in the overflow menu, so there's nothing to branch on.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/blog/release-2026-10-23/native-menu-submenu.jpg" rel="noopener noreferrer"&gt;&lt;img width="800" height="498" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-10-23%2Fnative-menu-submenu.jpg" alt="The word processor's File menu with an Export submenu on macOS"&gt;&lt;/a&gt;The word processor's File menu on macOS.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;About, Settings and Quit live in different places on every desktop, under different names. Give the command a role and each platform puts it where its own apps do, with its own shortcut:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Command&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createDesktopRole&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DESKTOP_ROLE_SETTINGS&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;showSettings&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="nc"&gt;Command&lt;/span&gt; &lt;span class="n"&gt;quit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createDesktopRole&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DESKTOP_ROLE_QUIT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;macOS&lt;/th&gt;
&lt;th&gt;Windows&lt;/th&gt;
&lt;th&gt;Linux&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;About&lt;/td&gt;
&lt;td&gt;Application menu, first&lt;/td&gt;
&lt;td&gt;Help, last&lt;/td&gt;
&lt;td&gt;Help, last&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Settings&lt;/td&gt;
&lt;td&gt;"Settings…" in the application menu, Cmd+,&lt;/td&gt;
&lt;td&gt;"Options" under Tools&lt;/td&gt;
&lt;td&gt;"Preferences" at the foot of Edit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quit&lt;/td&gt;
&lt;td&gt;"Quit" in the application menu, Cmd+Q&lt;/td&gt;
&lt;td&gt;"Exit" at the foot of File&lt;/td&gt;
&lt;td&gt;"Quit" at the foot of File, Ctrl+Q&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The File and Edit roles work the same way, so Redo is Cmd+Shift+Z on a Mac and Ctrl+Y on Windows without you looking it up. On macOS a role replaces the built-in item, so the menu never shows two Quit items.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/blog/release-2026-10-23/native-menu-app-roles.jpg" rel="noopener noreferrer"&gt;&lt;img width="800" height="480" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-10-23%2Fnative-menu-app-roles.jpg" alt="The macOS application menu with About, Settings and Quit"&gt;&lt;/a&gt;Roles landing in the macOS application menu.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;One more small thing that makes an editor feel right: &lt;code&gt;form.setDocumentModified(true)&lt;/code&gt; puts the dot in the close button on macOS and an asterisk before the title on Windows and Linux, and the Save command enables itself while there's something to save.&lt;/p&gt;

&lt;h2&gt;
  
  
  Animated 3D models
&lt;/h2&gt;

&lt;p&gt;The other big addition came from &lt;a href="https://www.codenameone.com/blog/build-a-multiplayer-strategy-game-in-java/" rel="noopener noreferrer"&gt;Skirmish&lt;/a&gt;. &lt;code&gt;GltfLoader&lt;/code&gt; loads glTF 2.0 models, binary &lt;code&gt;.glb&lt;/code&gt; or JSON &lt;code&gt;.gltf&lt;/code&gt;, together with their textures, so a textured model renders with no extra setup. &lt;code&gt;AnimatedModel&lt;/code&gt; brings them to life:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;GltfScene&lt;/span&gt; &lt;span class="n"&gt;knight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GltfLoader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadScene&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;AnimatedModel&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AnimatedModel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;knight&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;play&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Walking_A"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameView&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// later&lt;/span&gt;
&lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;crossFade&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1H_Melee_Attack_Chop"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.15f&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It plays the model's animation clips, cross-fades between them, deforms skinned meshes with their skeleton and carries rigid parts, like a sword in a hand, along with their bones. Skinning runs on the CPU, so it looks identical on every port and needs nothing from the shaders. Many instances can share one loaded scene: the vertex data, clips and textures are shared, and only the posed vertices are per instance. That's how a Skirmish battle fills the arena with troops.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/developer-guide/img/gpu-model.png" rel="noopener noreferrer"&gt;&lt;img width="358" height="276" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fgpu-model.png" alt="The Khronos BoomBox glTF sample rendered with its own texture"&gt;&lt;/a&gt;A textured glTF model.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/skirmish-battle.png" rel="noopener noreferrer"&gt;&lt;img width="420" height="909" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Fskirmish-battle.png" alt="Animated characters in a Skirmish battle"&gt;&lt;/a&gt;Animated characters in Skirmish.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.codenameone.com/developer-guide/desktop-integration/" rel="noopener noreferrer"&gt;desktop integration guide&lt;/a&gt;, the &lt;a href="https://www.codenameone.com/developer-guide/io/" rel="noopener noreferrer"&gt;file system chapter&lt;/a&gt; and the &lt;a href="https://www.codenameone.com/developer-guide/3d-graphics/" rel="noopener noreferrer"&gt;3D graphics chapter&lt;/a&gt; have the details.&lt;/p&gt;

&lt;p&gt;Tomorrow, the last post of the week: &lt;a href="https://www.codenameone.com/blog/private-calls-and-safer-backends/" rel="noopener noreferrer"&gt;WebRTC Calls, Key Agreement and ORM Checks at Build Time&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>More of the Java API, and an Experiment With Unity Games</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Tue, 03 Nov 2026 20:24:05 +0000</pubDate>
      <link>https://dev.to/codenameone/more-of-the-java-api-and-an-experiment-with-unity-games-4ae6</link>
      <guid>https://dev.to/codenameone/more-of-the-java-api-and-an-experiment-with-unity-games-4ae6</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdpghl90ytlrz0a5xagq9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdpghl90ytlrz0a5xagq9.jpg" alt="Java code and a Unity game running on Codename One" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post is about code you already have: Java code you want to reuse, and, as an experiment, Unity games.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Codename One?&lt;/strong&gt; 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 &lt;a href="https://www.codenameone.com/" rel="noopener noreferrer"&gt;codenameone.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Much more of the Java class library
&lt;/h2&gt;

&lt;p&gt;Codename One always shipped a subset of the Java API. That kept apps small, but it also meant that a library from Maven Central or a utility class from your server often needed edits before it compiled. That gap is now much smaller. 116 classes and interfaces joined the class library:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;What arrived&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;java.util.function&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The functional interfaces: &lt;code&gt;Function&lt;/code&gt;, &lt;code&gt;BiFunction&lt;/code&gt;, &lt;code&gt;Supplier&lt;/code&gt;, &lt;code&gt;Predicate&lt;/code&gt; and the primitive variants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;java.util.stream&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The core of the streams API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;java.util&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Optional&lt;/code&gt;, &lt;code&gt;StringJoiner&lt;/code&gt;, &lt;code&gt;UUID&lt;/code&gt;, &lt;code&gt;Base64&lt;/code&gt;, &lt;code&gt;HexFormat&lt;/code&gt;, &lt;code&gt;Spliterator&lt;/code&gt; and more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;java.util.concurrent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Executors&lt;/code&gt;, &lt;code&gt;Future&lt;/code&gt;, &lt;code&gt;Callable&lt;/code&gt; and friends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;java.time&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LocalDate&lt;/code&gt;, &lt;code&gt;LocalDateTime&lt;/code&gt;, &lt;code&gt;Instant&lt;/code&gt;, &lt;code&gt;Duration&lt;/code&gt;, &lt;code&gt;ZonedDateTime&lt;/code&gt; and the rest of the basics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;java.text&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SimpleDateFormat&lt;/code&gt;, &lt;code&gt;DecimalFormat&lt;/code&gt;, &lt;code&gt;MessageFormat&lt;/code&gt;, &lt;code&gt;NumberFormat&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;java.io&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;BufferedReader&lt;/code&gt;, &lt;code&gt;BufferedWriter&lt;/code&gt;, &lt;code&gt;PrintWriter&lt;/code&gt;, &lt;code&gt;InputStreamReader&lt;/code&gt; and other readers and writers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;String's regular expression methods now go through one engine on every platform, so &lt;code&gt;split&lt;/code&gt;, &lt;code&gt;matches&lt;/code&gt; and &lt;code&gt;replaceAll&lt;/code&gt; behave the same on iOS, Android and the browser.&lt;/p&gt;

&lt;p&gt;The obvious worry is size. I measured it, and the overhead is minor. ParparVM and the obfuscator strip whatever your app doesn't call, so an app that never touches streams doesn't carry them.&lt;/p&gt;

&lt;p&gt;What hasn't changed: there's still no reflection and no class loading. That's a deliberate choice, and it's a big part of why Codename One apps are small, start fast and can be translated ahead of time. I talked about this, and about why Java is a better fit for this world than people assume, in &lt;a href="https://www.youtube.com/watch?v=smHzWWNOyMU" rel="noopener noreferrer"&gt;my interview with Frank&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Unity 2D games: would this be useful?
&lt;/h2&gt;

&lt;p&gt;This one is an experiment, and I'd like your opinion on it.&lt;/p&gt;

&lt;p&gt;We wanted to learn what Unity does right for game developers and what parts of that we can offer. The best way to learn was to try running Unity games. So we built it: a Unity 2D project, with its C# scripts, scenes, prefabs, sprites and settings, copied into a Codename One project. The build compiles the C#, translates it to Java bytecode and runs it on every Codename One target, including iOS, Android, the browser and the desktop.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/developer-guide/img/unity-interop-fruitopia.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="450" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Funity-interop-fruitopia.png" alt="Fruitopia, an open source Unity platformer, running on Codename One"&gt;&lt;/a&gt;Fruitopia, a tile map platformer.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/unity-interop-asteroids.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="450" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Funity-interop-asteroids.png" alt="An open source Unity Asteroids game running on Codename One"&gt;&lt;/a&gt;Asteroids.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/unity-interop-snake.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="450" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Funity-interop-snake.png" alt="An open source Unity Snake game running on Codename One"&gt;&lt;/a&gt;Snake.&lt;br&gt;
&lt;a href="https://www.codenameone.com/developer-guide/img/unity-interop-sokoban.png" rel="noopener noreferrer"&gt;&lt;img width="800" height="450" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fdeveloper-guide%2Fimg%2Funity-interop-sokoban.png" alt="An open source Unity Sokoban game running on Codename One"&gt;&lt;/a&gt;Sokoban.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Five open source games run with no change to their scripts or assets. No Unity player and no .NET runtime ship with the app. Scripts become ordinary classes, scenes become generated code, and a runtime that implements the &lt;code&gt;UnityEngine&lt;/code&gt; API draws the game into a Codename One component. The .NET SDK is only needed on the machine that builds, and no Unity install or license is needed at all.&lt;/p&gt;

&lt;p&gt;That component is the interesting part. The game sits on a Codename One form, so everything around it is regular Codename One: a toolbar, a side menu, a store, sign-in, in-app purchases, push, and ads. Java and the game's scripts call each other:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="nc"&gt;Form&lt;/span&gt; &lt;span class="nf"&gt;createForm&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UnityGameView&lt;/span&gt; &lt;span class="n"&gt;gameView&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Form&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Form&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BorderLayout&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="nc"&gt;Toolbar&lt;/span&gt; &lt;span class="n"&gt;toolbar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Toolbar&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setToolbar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toolbar&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTitle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"My Game"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;toolbar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addMaterialCommandToRightBar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;FontImage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MATERIAL_LEADERBOARD&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;callInFrame&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ShowLeaderboard&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;get_Best&lt;/span&gt;&lt;span class="o"&gt;())));&lt;/span&gt;
    &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BorderLayout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CENTER&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gameView&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scope is 2D: sprites, tile maps, 2D physics, animation controllers, particles, the canvas interface and scripts. You can't take any Unity game and run it. Working on this improved the Codename One gaming API too, and those improvements are what Skirmish's arena uses.&lt;/p&gt;

&lt;p&gt;So, would this be useful to you? A way to bring a small Unity game into an app that also needs real app features, or to keep a game going on targets you don't want to pay for? Tell me in the comments below. Your answer decides how far we take it. The &lt;a href="https://www.codenameone.com/developer-guide/unity-interop/" rel="noopener noreferrer"&gt;Unity guide&lt;/a&gt; has the details.&lt;/p&gt;

&lt;h2&gt;
  
  
  A faster XML parser
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;XMLParser&lt;/code&gt; got a round of fixes and a lot faster. The fast path needs one call, because strict parsing follows the XML rules exactly where the classic parser was forgiving of HTML-like input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;XMLParser&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;XMLParser&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStrict&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStripNamespacePrefixes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// w:p is reported as p&lt;/span&gt;
&lt;span class="nc"&gt;Element&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the desktop JIT a benchmark document went from 13.8 ms to 8.3 ms. Interpreted, where it matters most on older devices, it went from 751 ms to 488 ms, and to 358 ms in strict mode. &lt;code&gt;Mappers.fromXml&lt;/code&gt; uses it, so XML mapped to objects gets the same speedup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the samples right in the docs
&lt;/h2&gt;

&lt;p&gt;Every sample in the API docs now has a button that runs it, labeled &lt;strong&gt;Try It&lt;/strong&gt;. Press it and the code compiles and runs in your browser, with the in-tree Java compiler and the ParparVM JavaScript translator. After the first load, a snippet takes about a second. Change the code and run it again.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://www.codenameone.com/blog/release-2026-10-23/try-it-javadoc.jpg" rel="noopener noreferrer"&gt;&lt;img width="800" height="562" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.codenameone.com%2Fblog%2Frelease-2026-10-23%2Ftry-it-javadoc.jpg" alt="The Try It button running a Button sample on the Codename One API docs"&gt;&lt;/a&gt;Try It on the Button docs page.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Tomorrow: &lt;a href="https://www.codenameone.com/blog/desktop-dialogs-menus-3d-models/" rel="noopener noreferrer"&gt;Native Menus, File Dialogs and 3D Models&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
  </channel>
</rss>
