<?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>Why Codename One Is Moving Beyond Maven Central</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Tue, 11 Aug 2026 14:16:04 +0000</pubDate>
      <link>https://dev.to/codenameone/why-codename-one-is-moving-beyond-maven-central-35f2</link>
      <guid>https://dev.to/codenameone/why-codename-one-is-moving-beyond-maven-central-35f2</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%2Frrf20ehw6mwh24xqkulw.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%2Frrf20ehw6mwh24xqkulw.jpg" alt="Codename One Maven artifacts move through a staged Central and Cloudflare R2 migration" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Codename One is starting a staged move from Maven Central to a repository we operate on Cloudflare R2.&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;This is not a story about Maven Central being bad. Sonatype runs expensive public infrastructure and has every right to define usage limits or sell a commercial service. Our release shape is simply a bad fit for those limits, and passing that infrastructure bill to Codename One users would make less sense than serving the same signed Maven layout ourselves.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5497" rel="noopener noreferrer"&gt;PR #5497&lt;/a&gt; implements phase one. It reduces what each release publishes and adds dual publication to R2. Maven Central remains authoritative during this phase.&lt;/p&gt;

&lt;p&gt;We first talked about the repository move in &lt;a href="https://www.codenameone.com/blog/push-v3-new-cloud/" rel="noopener noreferrer"&gt;Friday's release post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our dashboard is not a near miss
&lt;/h2&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9gm4mat8rr0kjcu2t8pa.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9gm4mat8rr0kjcu2t8pa.png" alt="Maven Central publishing usage shows Codename One far beyond the soft guidelines" width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The current Central Publishing usage dashboard reports:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Codename One&lt;/th&gt;
&lt;th&gt;Dashboard guideline&lt;/th&gt;
&lt;th&gt;Reported use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;2.12 GB&lt;/td&gt;
&lt;td&gt;80 MB&lt;/td&gt;
&lt;td&gt;2,652%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Files&lt;/td&gt;
&lt;td&gt;19,962&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;1,996%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Releases&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;386%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That screenshot also explains Sonatype's position. It says the guidelines are soft, describes adjustments for qualifying open-source projects, and offers a commercial Publisher Pro route. We are an unusually heavy publisher, not an innocent bystander being charged for one small JAR.&lt;/p&gt;

&lt;p&gt;The question is who should carry the cost. In &lt;a href="https://debugagent.com/open-source-bait-and-switch" rel="noopener noreferrer"&gt;Open Source Bait and Switch&lt;/a&gt;, I argued that monetization pressure often lands on small open-source vendors while the largest companies capture much of the value. Maven hosting is a different product and Sonatype is not changing our license. The same asymmetry is still relevant: charging a small framework company does not necessarily collect from the enterprise organizations receiving the largest downstream benefit.&lt;/p&gt;

&lt;p&gt;We can provide package hosting free to Codename One developers on infrastructure that matches our release process. That is the more sustainable answer for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  The migration has three visible dates
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;User impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;July 31, 2026&lt;/td&gt;
&lt;td&gt;Reduce release payload and start dual publishing&lt;/td&gt;
&lt;td&gt;None. Central remains authoritative.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;August 7, 2026&lt;/td&gt;
&lt;td&gt;Generated projects and Initializr add the Codename One repository&lt;/td&gt;
&lt;td&gt;New projects receive the repository automatically. Existing projects can add it manually.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;August 28, 2026&lt;/td&gt;
&lt;td&gt;Stop publishing new Codename One versions to Central, if the dual-publish period is clean&lt;/td&gt;
&lt;td&gt;Existing projects need the Codename One repository to discover future versions.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Dates are more useful than “next week” in a migration document. If validation changes the schedule, we will update this post before changing the source of new releases.&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%2FZmxvd2NoYXJ0IExSCiAgICBBWyJQaGFzZSAxPGJyLz5TaHJpbmsgYW5kIGR1YWwgcHVibGlzaCJdIC0tPiBCWyJQaGFzZSAyPGJyLz5HZW5lcmF0ZWQgUE9NcyB1c2UgUjIiXQogICAgQiAtLT4gQ1siVGhyZWUtd2VlayBvYnNlcnZhdGlvbiB3aW5kb3ciXQogICAgQyAtLT4gRFsiTmV3IHJlbGVhc2VzIG9uIFIyIl0KICAgIEEgLS0-IEVbIk1hdmVuIENlbnRyYWwgcmVtYWlucyBhdXRob3JpdGF0aXZlIl0KICAgIEIgLS0-IEUKICAgIEUgLS0-IEZbIkV4aXN0aW5nIENlbnRyYWwgdmVyc2lvbnMgcmVtYWluIGF2YWlsYWJsZSJd%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%2FZmxvd2NoYXJ0IExSCiAgICBBWyJQaGFzZSAxPGJyLz5TaHJpbmsgYW5kIGR1YWwgcHVibGlzaCJdIC0tPiBCWyJQaGFzZSAyPGJyLz5HZW5lcmF0ZWQgUE9NcyB1c2UgUjIiXQogICAgQiAtLT4gQ1siVGhyZWUtd2VlayBvYnNlcnZhdGlvbiB3aW5kb3ciXQogICAgQyAtLT4gRFsiTmV3IHJlbGVhc2VzIG9uIFIyIl0KICAgIEEgLS0-IEVbIk1hdmVuIENlbnRyYWwgcmVtYWlucyBhdXRob3JpdGF0aXZlIl0KICAgIEIgLS0-IEUKICAgIEUgLS0-IEZbIkV4aXN0aW5nIENlbnRyYWwgdmVyc2lvbnMgcmVtYWluIGF2YWlsYWJsZSJd%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1152" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The POM change
&lt;/h2&gt;

&lt;p&gt;Existing projects do not need to change during phase one. To prepare a project for new releases after the cutover, add both a repository and a plugin repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;repositories&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;repository&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;codenameone&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;https://repo.codenameone.com/maven2&lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;releases&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;enabled&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/enabled&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/releases&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;snapshots&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;enabled&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/enabled&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/snapshots&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/repository&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/repositories&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;pluginRepositories&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;pluginRepository&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;codenameone-plugins&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;https://repo.codenameone.com/maven2&lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;releases&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;enabled&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/enabled&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/releases&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;snapshots&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;enabled&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/enabled&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/snapshots&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/pluginRepository&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/pluginRepositories&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin repository is not redundant. Maven resolves build plugins and ordinary dependencies through separate repository lists. A project with only &lt;code&gt;&amp;lt;repositories&amp;gt;&lt;/code&gt; can download a runtime artifact and still fail to discover a future &lt;code&gt;codenameone-maven-plugin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next week's Initializr and archetype change will generate this configuration. Existing projects can add it at any time; the repository uses standard Maven layout and does not change dependency coordinates.&lt;/p&gt;

&lt;h2&gt;
  
  
  We were publishing the same bytes repeatedly
&lt;/h2&gt;

&lt;p&gt;The real Codename One 7.0.258 release published 44 artifacts and 127 JARs totaling 229.5 MB. The core JAR itself is only 4.49 MB. Most of the weight came from shaded fat JARs that republished the same third-party libraries each week.&lt;/p&gt;

&lt;p&gt;Phase one reduces the measured release payload to 76.9 MB, a 66 percent reduction:&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;Per release&lt;/th&gt;
&lt;th&gt;Versions in 8 GB&lt;/th&gt;
&lt;th&gt;Approximate history at 65 versions per year&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Before&lt;/td&gt;
&lt;td&gt;229.5 MB&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;6 months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After&lt;/td&gt;
&lt;td&gt;76.9 MB&lt;/td&gt;
&lt;td&gt;106&lt;/td&gt;
&lt;td&gt;1.6 years&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The changes are deliberately targeted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Five unused &lt;code&gt;jar-with-dependencies&lt;/code&gt; attachments stop being published. Builder bundles still contain what they need.&lt;/li&gt;
&lt;li&gt;Stable &lt;code&gt;designer&lt;/code&gt;, &lt;code&gt;javase-svg&lt;/code&gt;, and &lt;code&gt;sqlite-jdbc&lt;/code&gt; artifacts are frozen at pinned versions instead of being copied into every release.&lt;/li&gt;
&lt;li&gt;The headless CSS command moves out of the 43.5 MB Resource Editor fat JAR.&lt;/li&gt;
&lt;li&gt;A new &lt;code&gt;css-cli&lt;/code&gt; module carries that command in a 28 KB JAR.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CSS compiler itself had already been separated. Its command-line driver had not. Moving that driver avoids launching the complete Swing editor dependency graph for a headless build.&lt;/p&gt;

&lt;p&gt;This optimization would not get us under the dashboard guidelines. It is still worth doing. It reduces upload time, metadata churn, retention pressure, and the number of bytes a failed release has to retry.&lt;/p&gt;

&lt;h2&gt;
  
  
  R2 is static hosting, which is what Maven needs
&lt;/h2&gt;

&lt;p&gt;A release repository does not need a database-driven artifact server in the request path. Maven needs immutable files in a known directory layout, metadata, checksums, and signatures.&lt;/p&gt;

&lt;p&gt;The release pipeline uploads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;maven2/com/codenameone/&amp;lt;artifactId&amp;gt;/&amp;lt;version&amp;gt;/...
maven2/com/codenameone/&amp;lt;artifactId&amp;gt;/maven-metadata.xml
maven2/com/codenameone/maven-metadata.xml
maven2/archetype-catalog.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The R2 publication reuses the tree already staged for Central. It includes MD5, SHA-1, SHA-256, SHA-512, and &lt;code&gt;.asc&lt;/code&gt; signatures. There is no second Maven build that could produce different bytes.&lt;/p&gt;

&lt;p&gt;Cloudflare's edge cache and object storage should reduce download latency and remove Central throttling from our release path. We also expect releases and CI to become faster and more stable because publication no longer depends on a second service accepting our volume at that moment. Those are expectations, not measurements yet. We will compare publish time, artifact resolution time, cache behavior, and failure rate during dual publication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static hosting still needs release semantics
&lt;/h2&gt;

&lt;p&gt;Object storage will happily accept a partial or overwritten release unless the publishing scripts stop it. The phase-one tooling adds those rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Released bytes are immutable. An upload fails if the same path already contains different content.&lt;/li&gt;
&lt;li&gt;A release is invisible until every core and editor artifact is present.&lt;/li&gt;
&lt;li&gt;A per-release completion marker is written only after all uploads succeed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maven-metadata.xml&lt;/code&gt; is rebuilt from the bucket listing, never from the one-version staging tree.&lt;/li&gt;
&lt;li&gt;Metadata advertises only versions with a completion marker.&lt;/li&gt;
&lt;li&gt;Upload uses a copy operation that cannot delete older versions.&lt;/li&gt;
&lt;li&gt;Frozen dependencies are seeded from Central and verified against their checksums.&lt;/li&gt;
&lt;/ol&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%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBCdWlsZCBhcyBSZWxlYXNlIGJ1aWxkCiAgICBwYXJ0aWNpcGFudCBTdGFnZSBhcyBDZW50cmFsIHN0YWdpbmcgdHJlZQogICAgcGFydGljaXBhbnQgUjIgYXMgUjIgYnVja2V0CiAgICBwYXJ0aWNpcGFudCBNZXRhIGFzIE1ldGFkYXRhIGdlbmVyYXRvcgogICAgQnVpbGQtPj5TdGFnZTogUHJvZHVjZSBzaWduZWQgYXJ0aWZhY3RzIGFuZCBjaGVja3N1bXMKICAgIFN0YWdlLT4-UjI6IENvcHkgd2l0aG91dCBkZWxldGUKICAgIFIyLT4-UjI6IFJlamVjdCBkaWZmZXJlbnQgYnl0ZXMgYXQgYW4gZXhpc3RpbmcgcGF0aAogICAgQnVpbGQtPj5SMjogTWFyayBjb21wbGV0ZSBhZnRlciBldmVyeSBjb21wb25lbnQgc3VjY2VlZHMKICAgIE1ldGEtPj5SMjogTGlzdCBjb21wbGV0ZSB2ZXJzaW9ucwogICAgTWV0YS0-PlIyOiBXcml0ZSBtZXRhZGF0YSBsYXN0CiAgICBOb3RlIG92ZXIgUjI6IFBhcnRpYWwgcmVsZWFzZXMgcmVtYWluIHVuZGlzY292ZXJhYmxl%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%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBCdWlsZCBhcyBSZWxlYXNlIGJ1aWxkCiAgICBwYXJ0aWNpcGFudCBTdGFnZSBhcyBDZW50cmFsIHN0YWdpbmcgdHJlZQogICAgcGFydGljaXBhbnQgUjIgYXMgUjIgYnVja2V0CiAgICBwYXJ0aWNpcGFudCBNZXRhIGFzIE1ldGFkYXRhIGdlbmVyYXRvcgogICAgQnVpbGQtPj5TdGFnZTogUHJvZHVjZSBzaWduZWQgYXJ0aWZhY3RzIGFuZCBjaGVja3N1bXMKICAgIFN0YWdlLT4-UjI6IENvcHkgd2l0aG91dCBkZWxldGUKICAgIFIyLT4-UjI6IFJlamVjdCBkaWZmZXJlbnQgYnl0ZXMgYXQgYW4gZXhpc3RpbmcgcGF0aAogICAgQnVpbGQtPj5SMjogTWFyayBjb21wbGV0ZSBhZnRlciBldmVyeSBjb21wb25lbnQgc3VjY2VlZHMKICAgIE1ldGEtPj5SMjogTGlzdCBjb21wbGV0ZSB2ZXJzaW9ucwogICAgTWV0YS0-PlIyOiBXcml0ZSBtZXRhZGF0YSBsYXN0CiAgICBOb3RlIG92ZXIgUjI6IFBhcnRpYWwgcmVsZWFzZXMgcmVtYWluIHVuZGlzY292ZXJhYmxl%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="990" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cloudflare can cache a 404 just before a new artifact appears. The runbook therefore requires a cache rule that does not store 4xx and 5xx responses. Release polling also uses a cache-busting query so a stale negative cannot turn a successful upload into a false release failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retention: the guarantee and the likely window
&lt;/h2&gt;

&lt;p&gt;We will guarantee at least six months of historical Codename One versions in the new repository. The optimized payload currently fits roughly 106 releases in the planned capacity, which is about 1.6 years at the recent pace.&lt;/p&gt;

&lt;p&gt;The guarantee is shorter than the estimate because release contents and frequency can change. We would rather guarantee a window we can defend and retain more in practice than publish a long promise that quietly fails.&lt;/p&gt;

&lt;p&gt;Freezing stable packages also means “history” is no longer one full copy of every byte per weekly tag. A future version can depend on a pinned stable artifact while the frequently changing core keeps its own version cadence.&lt;/p&gt;

&lt;p&gt;Versions already on Maven Central are not deleted by this move. The retention policy applies to the repository we operate and to future availability, not to removing the public history that Central already stores.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can still go wrong
&lt;/h2&gt;

&lt;p&gt;The red-team list is part of the design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A partial upload is advertised.&lt;/strong&gt; Completion markers and metadata-last publication prevent it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A retry changes released bytes.&lt;/strong&gt; The overwrite guard fails the release.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A stale cached 404 hides a new file.&lt;/strong&gt; The cache rule and cache-busted poll address it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A project adds only the dependency repository.&lt;/strong&gt; Generated POMs include &lt;code&gt;&amp;lt;pluginRepositories&amp;gt;&lt;/code&gt; too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A frozen artifact remains available only on Central.&lt;/strong&gt; The seed step copies and verifies every pinned dependency before the final cutover.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;R2 is slower in a real region.&lt;/strong&gt; The observation window measures this before Central publication stops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three release tags arrive while one is running.&lt;/strong&gt; GitHub Actions can replace a pending member of a concurrency group. The current runbook requires pushing release tags one at a time until dispatch is made durable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last item is not polished away. The repository removes one availability dependency, but it does not turn GitHub Actions into a release queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this should be mostly seamless
&lt;/h2&gt;

&lt;p&gt;Coordinates, version numbers, signatures, checksums, and normal Maven resolution remain unchanged. New projects receive the repository automatically next week. Existing projects need one POM block before they request a post-cutover version.&lt;/p&gt;

&lt;p&gt;That is the whole user-facing migration. The work underneath it is larger because package hosting is part of the product supply chain. If we do this well, developers will notice faster and more reliable resolution, not a new thing they have to learn.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>A Polyline Is Not a Route</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Mon, 10 Aug 2026 14:43:58 +0000</pubDate>
      <link>https://dev.to/codenameone/a-polyline-is-not-a-route-5fek</link>
      <guid>https://dev.to/codenameone/a-polyline-is-not-a-route-5fek</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%2F6h8mdnsm93d07gxbwxna.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%2F6h8mdnsm93d07gxbwxna.jpg" alt="A road-following route replaces a straight line between two map pins" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A polyline can draw the points you give it. It cannot discover the road between them.&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;a href="https://github.com/codenameone/CodenameOne/pull/5480" rel="noopener noreferrer"&gt;PR #5480&lt;/a&gt; adds &lt;code&gt;com.codename1.maps.routing&lt;/code&gt;, a portable route model and service layer for road geometry, distance, duration, waypoints, alternatives, legs, steps, and encoded polylines.&lt;/p&gt;

&lt;p&gt;We talked about routing in &lt;a href="https://www.codenameone.com/blog/push-v3-new-cloud/" rel="noopener noreferrer"&gt;Friday's release post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two-line route
&lt;/h2&gt;

&lt;p&gt;For the common case, give &lt;code&gt;Routing&lt;/code&gt; a map and two coordinates:&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;MapView&lt;/span&gt; &lt;span class="n"&gt;map&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;MapView&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;Routing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;showRoute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LatLng&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;38.8977&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;77.0365&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LatLng&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;38.8894&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;77.0352&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 call returns immediately. The routing service finds the best route, the API adds its polyline, and the map frames the result.&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%2FZmxvd2NoYXJ0IExSCiAgICBBWyJPcmlnaW4gYW5kIGRlc3RpbmF0aW9uIl0gLS0-IEJbIlJvdXRlUmVxdWVzdCJdCiAgICBCIC0tPiBDWyJSb3V0ZVNlcnZpY2UiXQogICAgQyAtLT4gRFsiUm9hZCBuZXR3b3JrIGNhbGN1bGF0aW9uIl0KICAgIEQgLS0-IEVbIlJvdXRlLCBsZWdzLCBzdGVwcywgZGlzdGFuY2UsIGR1cmF0aW9uIl0KICAgIEUgLS0-IEZbIlBvbHlsaW5lIG9uIE1hcFN1cmZhY2UiXQogICAgRSAtLT4gR1siQXBwbGljYXRpb24gVUkiXQ%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%2FZmxvd2NoYXJ0IExSCiAgICBBWyJPcmlnaW4gYW5kIGRlc3RpbmF0aW9uIl0gLS0-IEJbIlJvdXRlUmVxdWVzdCJdCiAgICBCIC0tPiBDWyJSb3V0ZVNlcnZpY2UiXQogICAgQyAtLT4gRFsiUm9hZCBuZXR3b3JrIGNhbGN1bGF0aW9uIl0KICAgIEQgLS0-IEVbIlJvdXRlLCBsZWdzLCBzdGVwcywgZGlzdGFuY2UsIGR1cmF0aW9uIl0KICAgIEUgLS0-IEZbIlBvbHlsaW5lIG9uIE1hcFN1cmZhY2UiXQogICAgRSAtLT4gR1siQXBwbGljYXRpb24gVUkiXQ%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1528" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;showRoute(...)&lt;/code&gt; is intentionally small. It is useful when the line itself is the result. An application that needs error UI, custom styling, ETA, or alternatives should use the callback route.&lt;/p&gt;

&lt;h2&gt;
  
  
  Own the result when it matters
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;RouteRequest&lt;/span&gt; &lt;span class="n"&gt;request&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;RouteRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTravelMode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TravelMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DRIVING&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addWaypoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coffeeStop&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAlternatives&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;setSteps&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="nc"&gt;Routing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findRoute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&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;RouteCallback&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;routesFound&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;routes&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="n"&gt;map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addPolyline&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toPolyline&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStrokeColor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xff5722&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStrokeWidth&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fitBounds&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBounds&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;distanceLabel&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="nc"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;round&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDistanceMeters&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1000.0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" km"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;etaLabel&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="nc"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;round&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDurationSeconds&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;60.0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" min"&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;routeFailed&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;message&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Throwable&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;ToastBar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;showErrorMessage&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;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 callback is invoked exactly once and always later on the Codename One EDT, even if a custom service responds synchronously, responds from a worker thread, responds twice, or throws after responding. The facade contains those service errors so application timing does not depend on the provider implementation.&lt;/p&gt;

&lt;p&gt;The model exposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One or more route alternatives&lt;/li&gt;
&lt;li&gt;Total distance and duration&lt;/li&gt;
&lt;li&gt;Geographic bounds&lt;/li&gt;
&lt;li&gt;Route geometry&lt;/li&gt;
&lt;li&gt;Legs between waypoints&lt;/li&gt;
&lt;li&gt;Step instructions and maneuver locations&lt;/li&gt;
&lt;li&gt;Provider metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;PolylineCodec&lt;/code&gt; supports precision 5 and precision 6 encoded geometry. That lets a service retain the compact wire format and decode it only when a &lt;code&gt;Polyline&lt;/code&gt; is needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  OSRM makes the first run easy
&lt;/h2&gt;

&lt;p&gt;The default &lt;code&gt;RouteService&lt;/code&gt; is &lt;code&gt;OsrmRouteService&lt;/code&gt;. It needs no API key, so the two-line example can work without provider signup.&lt;/p&gt;

&lt;p&gt;That default points to the public OSRM demonstration server. The server has no production SLA and asks clients to keep usage light. It can reject large or abusive workloads, and its public profile is configured for cars.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DRIVING&lt;/code&gt; is appropriate for a quick test.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WALKING&lt;/code&gt; or &lt;code&gt;CYCLING&lt;/code&gt; against the default demo can still return car routing.&lt;/li&gt;
&lt;li&gt;A shipping application should use a provider and capacity it controls.&lt;/li&gt;
&lt;li&gt;Offline routing requires another implementation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Travel mode is a request, not a guarantee. A &lt;code&gt;RouteService&lt;/code&gt; reports what it supports, and an application should not label a result “walking” when the active backend only has a car graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Point OSRM at infrastructure you control
&lt;/h2&gt;

&lt;p&gt;OSRM is open source and can be self-hosted. If your server exposes an OSRM-compatible route endpoint, install it as the application service:&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;Routing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setService&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;OsrmRouteService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"https://routing.example.com"&lt;/span&gt;
&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the exact constructor and endpoint configuration supported by your selected release. The architectural point is that application code still consumes &lt;code&gt;Route&lt;/code&gt;, not provider JSON.&lt;/p&gt;

&lt;p&gt;For a different provider, implement &lt;code&gt;RouteService&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="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CompanyRouteService&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;RouteService&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;getId&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="s"&gt;"company-routing"&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;boolean&lt;/span&gt; &lt;span class="nf"&gt;isAvailable&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="nf"&gt;credentialsAreReady&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;findRoutes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RouteRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RouteCallback&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Translate the portable request, call the provider,&lt;/span&gt;
        &lt;span class="c1"&gt;// then return portable Route objects through the callback.&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Routing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setService&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;CompanyRouteService&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That seam supports commercial providers, a company gateway, an offline engine, or a route service with domain constraints such as truck height and hazardous materials.&lt;/p&gt;

&lt;h2&gt;
  
  
  The maps API and routing API stay separate
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;MapSurface&lt;/code&gt; displays geometry. &lt;code&gt;RouteService&lt;/code&gt; discovers geometry. Keeping them separate avoids tying a route provider to one renderer.&lt;/p&gt;

&lt;p&gt;The same route can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drawn on a native or vector map&lt;/li&gt;
&lt;li&gt;Styled according to traffic or accessibility&lt;/li&gt;
&lt;li&gt;Summarized as distance and ETA&lt;/li&gt;
&lt;li&gt;Saved as an encoded polyline&lt;/li&gt;
&lt;li&gt;Compared with alternatives&lt;/li&gt;
&lt;li&gt;Sent to another screen without retaining the map component&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API does not yet claim turn-by-turn navigation, rerouting, traffic prediction, offline map packages, or voice guidance. Those are stateful products with location updates and provider-specific rules. This release provides the route result that those systems need, without naming a static polyline “navigation.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Production checklist
&lt;/h2&gt;

&lt;p&gt;Before shipping:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose a routing service with terms, capacity, data coverage, and travel profiles that fit the application.&lt;/li&gt;
&lt;li&gt;Report route failures to the user. The no-callback &lt;code&gt;showRoute(...)&lt;/code&gt; convenience method cannot do that.&lt;/li&gt;
&lt;li&gt;Verify units before displaying them. The model uses meters and seconds.&lt;/li&gt;
&lt;li&gt;Test dateline crossing, unreachable points, ferries, tolls, and waypoint order for the markets you serve.&lt;/li&gt;
&lt;li&gt;Keep provider keys out of client code when the provider expects a server-side secret.&lt;/li&gt;
&lt;li&gt;Attribute map and routing data according to each provider's license.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A route is an answer from a changing road graph, not a decorative line. The new API finally represents it that way.&lt;/p&gt;

&lt;p&gt;Next in the series: &lt;a href="https://www.codenameone.com/blog/maven-central-cloudflare-r2/" rel="noopener noreferrer"&gt;why Codename One is starting a staged move beyond Maven Central&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>On-Device AI and MCP on Every Port</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Sun, 09 Aug 2026 13:43:42 +0000</pubDate>
      <link>https://dev.to/codenameone/on-device-ai-and-mcp-on-every-port-9a2</link>
      <guid>https://dev.to/codenameone/on-device-ai-and-mcp-on-every-port-9a2</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%2Fjvwgnyxuqygsaiflrl0o.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%2Fjvwgnyxuqygsaiflrl0o.jpg" alt="An AI agent inspects a mobile application through a guarded local connection" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are two very different ways to connect AI to an application.&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;One puts the model inside the feature: OCR a receipt, identify a language, segment a person, or run an application-owned model.&lt;/p&gt;

&lt;p&gt;The other lets an agent stand outside the application, inspect its semantic UI, and operate it while a developer watches. That second path is extraordinarily useful for debugging. It is also a control channel, so where it listens matters.&lt;/p&gt;

&lt;p&gt;This week Codename One merged both parts: &lt;a href="https://github.com/codenameone/CodenameOne/pull/5467" rel="noopener noreferrer"&gt;built-in on-device AI&lt;/a&gt; and &lt;a href="https://github.com/codenameone/CodenameOne/pull/5472" rel="noopener noreferrer"&gt;MCP over a loopback socket on mobile and desktop ports&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We talked about both changes in &lt;a href="https://www.codenameone.com/blog/push-v3-new-cloud/" rel="noopener noreferrer"&gt;Friday's release post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI API is selective, local, and portable
&lt;/h2&gt;

&lt;p&gt;The new core surface is split by job:&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;Capabilities&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;com.codename1.ai.vision&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OCR, barcode recognition, face detection, image labels, pose detection, selfie segmentation, and document correction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;com.codename1.ai.language&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Language identification, translation, and smart reply&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;com.codename1.ai.inference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reusable application-owned &lt;code&gt;.tflite&lt;/code&gt; inference sessions and verified model downloads&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The public API exists on every target. A port that cannot perform an operation reports it as unsupported instead of sending data to a surprise cloud fallback.&lt;/p&gt;

&lt;p&gt;Android uses ML Kit for the higher-level vision and language operations. Apple platforms use Vision, Core Image, and Natural Language by default where those frameworks provide the feature. Optional ML Kit support fills selected gaps on iOS. LiteRT and TensorFlow Lite execute application-owned models, with platform acceleration where available and CPU fallback.&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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJDb2RlbmFtZSBPbmUgQUkgQVBJIl0gLS0-IEJbIlZpc2lvbiJdCiAgICBBIC0tPiBDWyJMYW5ndWFnZSJdCiAgICBBIC0tPiBEWyJJbmZlcmVuY2UiXQogICAgQiAtLT4gRVsiQW5kcm9pZCBNTCBLaXQiXQogICAgQiAtLT4gRlsiQXBwbGUgVmlzaW9uIGFuZCBDb3JlIEltYWdlIl0KICAgIEMgLS0-IEdbIk1MIEtpdCBvciBBcHBsZSBOYXR1cmFsIExhbmd1YWdlIl0KICAgIEQgLS0-IEhbIkxpdGVSVCAvIFRlbnNvckZsb3cgTGl0ZSJdCiAgICBIIC0tPiBJWyJOTkFQSSwgQ29yZSBNTCBkZWxlZ2F0ZSwgb3IgQ1BVIl0%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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJDb2RlbmFtZSBPbmUgQUkgQVBJIl0gLS0-IEJbIlZpc2lvbiJdCiAgICBBIC0tPiBDWyJMYW5ndWFnZSJdCiAgICBBIC0tPiBEWyJJbmZlcmVuY2UiXQogICAgQiAtLT4gRVsiQW5kcm9pZCBNTCBLaXQiXQogICAgQiAtLT4gRlsiQXBwbGUgVmlzaW9uIGFuZCBDb3JlIEltYWdlIl0KICAgIEMgLS0-IEdbIk1MIEtpdCBvciBBcHBsZSBOYXR1cmFsIExhbmd1YWdlIl0KICAgIEQgLS0-IEhbIkxpdGVSVCAvIFRlbnNvckZsb3cgTGl0ZSJdCiAgICBIIC0tPiBJWyJOTkFQSSwgQ29yZSBNTCBkZWxlZ2F0ZSwgb3IgQ1BVIl0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Only referenced feature families bring their native dependencies into a build. An application using language identification does not need the document scanner, pose detector, and model runtime by association.&lt;/p&gt;

&lt;h2&gt;
  
  
  OCR should look like an asynchronous operation
&lt;/h2&gt;

&lt;p&gt;The API owns native image conversion, lifecycle, and EDT delivery:&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;TextRecognizer&lt;/span&gt; &lt;span class="n"&gt;recognizer&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;TextRecognizer&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;recognizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;process&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;VisionImage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;encoded&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jpegBytes&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;result&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;textArea&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;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getText&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;except&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is structured, so an application can work with blocks and geometry instead of immediately flattening everything into one string.&lt;/p&gt;

&lt;p&gt;Model inference uses a session because loading and compiling a model for every frame is expensive:&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;InferenceSession&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;ModelSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modelFile&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InferenceOptions&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;session&lt;/span&gt; &lt;span class="o"&gt;-&amp;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="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputs&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;outputs&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;renderPrediction&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;except&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&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;Close a session when the screen or feature that owns it is done. A model downloaded at runtime can go through &lt;code&gt;ModelCache&lt;/code&gt;, which requires HTTPS and verifies the model's SHA-256 digest before the file becomes active. Model bytes are executable behavior in a different costume. Treating an unverified download as “just data” is a software supply chain mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local inference improves privacy, but does not solve it
&lt;/h2&gt;

&lt;p&gt;These APIs do not upload images, text, or tensors to Codename One. That is a useful privacy property, especially for camera frames and documents.&lt;/p&gt;

&lt;p&gt;It is not a complete privacy policy. A recognized receipt can still be logged. A translated medical note can still be copied into analytics. An application can still send an inference result to its own backend. On-device processing narrows the data path; the application remains responsible for what happens before and after the operation.&lt;/p&gt;

&lt;p&gt;The backend capabilities also differ. Smart reply is not available through every native framework. Apple Vision support extends to Mac Catalyst for vision tasks, while unsupported desktop and browser operations fail rather than simulating an answer. Check capabilities at the feature boundary and design a real fallback.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP makes the running application legible to an agent
&lt;/h2&gt;

&lt;p&gt;We introduced the &lt;a href="https://www.codenameone.com/blog/codename-one-mcp-server/" rel="noopener noreferrer"&gt;Codename One MCP server&lt;/a&gt; on JavaSE first. It exposes the semantic UI tree instead of screenshots and guessed coordinates.&lt;/p&gt;

&lt;p&gt;An agent can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read &lt;code&gt;ui_snapshot&lt;/code&gt; output&lt;/li&gt;
&lt;li&gt;Find a component by role, text, or semantic identifier&lt;/li&gt;
&lt;li&gt;Activate an action&lt;/li&gt;
&lt;li&gt;Set text through the component model&lt;/li&gt;
&lt;li&gt;Scroll the current viewport&lt;/li&gt;
&lt;li&gt;Observe the resulting UI state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The operations run on the Codename One EDT. A button press is a component action, not a coordinate that happens to land where the button was during one recording.&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;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&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;isDebuggableBuild&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;MCP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startSocketServer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8642&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;With the new loopback transport, the same portable MCP server can run on Android, iOS, and other ports that implement loopback listening. A developer can connect through the platform's normal device or simulator forwarding path and let an agent reproduce a bug in the actual application.&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%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBEZXYgYXMgRGV2ZWxvcGVyIGFuZCBMTE0gY2xpZW50CiAgICBwYXJ0aWNpcGFudCBQb3J0IGFzIERldmljZSBwb3J0IGZvcndhcmQKICAgIHBhcnRpY2lwYW50IE1DUCBhcyBMb29wYmFjayBNQ1Agc2VydmVyCiAgICBwYXJ0aWNpcGFudCBVSSBhcyBDb2RlbmFtZSBPbmUgRURUCiAgICBEZXYtPj5Qb3J0OiBDb25uZWN0IHRvIGRlYnVnIGRldmljZQogICAgUG9ydC0-Pk1DUDogRm9yd2FyZCB0byAxMjcuMC4wLjE6ODY0MgogICAgRGV2LT4-TUNQOiB1aV9zbmFwc2hvdAogICAgTUNQLT4-VUk6IFJlYWQgc2VtYW50aWMgdHJlZQogICAgVUktLT4-TUNQOiBSb2xlcywgbGFiZWxzLCBzdGF0ZSwgYWN0aW9ucwogICAgTUNQLS0-PkRldjogU3RydWN0dXJlZCBzbmFwc2hvdAogICAgRGV2LT4-TUNQOiB1aV9hY3RpdmF0ZSBzZW1hbnRpYyB0YXJnZXQKICAgIE1DUC0-PlVJOiBQZXJmb3JtIGFjdGlvbgogICAgVUktLT4-RGV2OiBVcGRhdGVkIHN0YXRl%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%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBEZXYgYXMgRGV2ZWxvcGVyIGFuZCBMTE0gY2xpZW50CiAgICBwYXJ0aWNpcGFudCBQb3J0IGFzIERldmljZSBwb3J0IGZvcndhcmQKICAgIHBhcnRpY2lwYW50IE1DUCBhcyBMb29wYmFjayBNQ1Agc2VydmVyCiAgICBwYXJ0aWNpcGFudCBVSSBhcyBDb2RlbmFtZSBPbmUgRURUCiAgICBEZXYtPj5Qb3J0OiBDb25uZWN0IHRvIGRlYnVnIGRldmljZQogICAgUG9ydC0-Pk1DUDogRm9yd2FyZCB0byAxMjcuMC4wLjE6ODY0MgogICAgRGV2LT4-TUNQOiB1aV9zbmFwc2hvdAogICAgTUNQLT4-VUk6IFJlYWQgc2VtYW50aWMgdHJlZQogICAgVUktLT4-TUNQOiBSb2xlcywgbGFiZWxzLCBzdGF0ZSwgYWN0aW9ucwogICAgTUNQLS0-PkRldjogU3RydWN0dXJlZCBzbmFwc2hvdAogICAgRGV2LT4-TUNQOiB1aV9hY3RpdmF0ZSBzZW1hbnRpYyB0YXJnZXQKICAgIE1DUC0-PlVJOiBQZXJmb3JtIGFjdGlvbgogICAgVUktLT4-RGV2OiBVcGRhdGVkIHN0YXRl%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="985" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This shortens a debugging conversation. Instead of “tap near the upper-right corner, unless the font is larger,” the agent can say “activate the button whose semantic text is Submit,” then inspect the error label that appeared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why wildcard listening was unacceptable
&lt;/h2&gt;

&lt;p&gt;The existing general server socket API could listen on every network interface. On a phone connected to office Wi-Fi, that can expose a debugging control channel to the local network.&lt;/p&gt;

&lt;p&gt;The new &lt;code&gt;Socket.listenLoopback(...)&lt;/code&gt; contract is different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It binds only to the loopback interface.&lt;/li&gt;
&lt;li&gt;It never falls back to wildcard listening.&lt;/li&gt;
&lt;li&gt;An unsupported port fails on the calling thread.&lt;/li&gt;
&lt;li&gt;The server does not appear reachable merely because a start method returned.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That prevents accidental LAN exposure. It does not make the channel private.&lt;/p&gt;

&lt;p&gt;Loopback is a host boundary, not an application identity boundary. Another process on the same device can try to connect. A compromised developer workstation can use an established port forward. Once connected, the client can read application UI state and invoke the actions the MCP server exposes.&lt;/p&gt;

&lt;p&gt;The safe assumption is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Starting MCP grants a local agent the ability to inspect and drive this application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is excellent in a debug session and a terrible surprise in a production build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Release builds are blocked by default
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;MCP.startSocketServer(port)&lt;/code&gt; checks &lt;code&gt;Display.isDebuggableBuild()&lt;/code&gt;. It refuses to start in release builds unless the application explicitly overrides the guard:&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="no"&gt;MCP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAllowOnReleaseBuilds&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="no"&gt;MCP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startSocketServer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8642&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That override exists for controlled test labs, managed fleets, and specialized internal deployments. Do not use it as a convenience flag in a consumer release.&lt;/p&gt;

&lt;p&gt;JavaSE is a special case because the desktop port reports a development environment. If you package a desktop application for end users, add your own product-level gate and omit the MCP startup path from normal launch.&lt;/p&gt;

&lt;p&gt;A defensible test-lab gate combines several conditions:&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;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isInternalTestAccount&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;isManagedTestDevice&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;wasMcpEnabledForThisSession&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;MCP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAllowOnReleaseBuilds&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="no"&gt;MCP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startSocketServer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8642&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 exact checks belong to the application. A hard-coded secret inside the binary is not an authentication system.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is debugging access, not autonomous product behavior
&lt;/h2&gt;

&lt;p&gt;On-device AI and MCP sit near each other in the release, but they should not be blurred together.&lt;/p&gt;

&lt;p&gt;The AI API runs bounded application features with explicit inputs and results. MCP exposes a developer tool channel into the running UI. Neither automatically turns an application into an autonomous agent, and neither should silently send user data to a model provider.&lt;/p&gt;

&lt;p&gt;The compelling workflow is smaller and more practical:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A developer launches a debuggable build.&lt;/li&gt;
&lt;li&gt;MCP starts on loopback.&lt;/li&gt;
&lt;li&gt;The development machine forwards the port.&lt;/li&gt;
&lt;li&gt;An LLM reads the semantic UI and reproduces a reported path.&lt;/li&gt;
&lt;li&gt;The developer sees the exact state transition and keeps control of the session.&lt;/li&gt;
&lt;li&gt;The server disappears when the debug process ends.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is enough to make mobile debugging dramatically more observable without pretending a control channel has no security cost.&lt;/p&gt;

&lt;p&gt;Next in the series: &lt;a href="https://www.codenameone.com/blog/road-following-map-routing/" rel="noopener noreferrer"&gt;road-following routes in the maps API&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Health Data Without Fake Certainty</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Sat, 08 Aug 2026 13:40:17 +0000</pubDate>
      <link>https://dev.to/codenameone/health-data-without-fake-certainty-fok</link>
      <guid>https://dev.to/codenameone/health-data-without-fake-certainty-fok</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%2F7asast0o1pxdf33axuuj.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%2F7asast0o1pxdf33axuuj.jpg" alt="Health data moving through explicit permissions, stores, sensors, and workouts" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The hardest part of a health API is not reading a heart-rate number. It is knowing what that number means, which store supplied it, whether anything is missing, and what the application is legally allowed to do next.&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;a href="https://github.com/codenameone/CodenameOne/pull/5475" rel="noopener noreferrer"&gt;PR #5475&lt;/a&gt; merged a cross-platform health API for HealthKit, Health Connect, recorded workouts, nutrition, Bluetooth health sensors, and deterministic simulation into the core. The implementation has compile, link, and automated coverage. Real-device testing remains important for store permissions and sensor hardware.&lt;/p&gt;

&lt;p&gt;We talked about this in &lt;a href="https://www.codenameone.com/blog/push-v3-new-cloud/" rel="noopener noreferrer"&gt;Friday's release post&lt;/a&gt;, alongside Push V3 and the new cloud migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The API has four public layers
&lt;/h2&gt;

&lt;p&gt;The packages are divided by the boundary they touch:&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;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;com.codename1.health&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Health stores, permissions, samples, units, queries, aggregates, sources, change subscriptions, and errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;com.codename1.health.workout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recorded workout sessions, events, configuration, and collected samples&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;com.codename1.health.sensors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Live standard Bluetooth health devices, independent of HealthKit or Health Connect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;com.codename1.health.nutrition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sparse nutrient records and nutrition-specific types&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;Health.getInstance()&lt;/code&gt; is the single entry point and never returns &lt;code&gt;null&lt;/code&gt;. An unsupported port returns a fallback whose operations fail with &lt;code&gt;HealthError.NOT_SUPPORTED&lt;/code&gt;. Application code branches on capabilities instead of operating-system names.&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;Health&lt;/span&gt; &lt;span class="n"&gt;health&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Health&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="nc"&gt;HealthAvailability&lt;/span&gt; &lt;span class="n"&gt;availability&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAvailability&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;availability&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;HealthAvailability&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PROVIDER_NOT_INSTALLED&lt;/span&gt;
        &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;availability&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;HealthAvailability&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PROVIDER_UPDATE_REQUIRED&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;openProviderSetup&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="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;availability&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;HealthAvailability&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NOT_SUPPORTED&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="nc"&gt;HealthStore&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStore&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requestAuthorization&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;HealthAccess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HealthDataType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STEPS&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;HealthAccess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HealthDataType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;HEART_RATE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;onResult&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;asked&lt;/span&gt;&lt;span class="o"&gt;,&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;-&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;err&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="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&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="c1"&gt;// asked means the sheet completed. It does not mean read access was granted.&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every result, sensor sample, workout event, and change batch arrives on the Codename One EDT. That rule is the same on a phone, in the simulator, and in a local desktop store.&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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJBcHBsaWNhdGlvbiJdIC0tPiBCWyJIZWFsdGgiXQogICAgQiAtLT4gQ1siSGVhbHRoU3RvcmUiXQogICAgQiAtLT4gRFsiV29ya291dE1hbmFnZXIiXQogICAgQiAtLT4gRVsiSGVhbHRoU2Vuc29ycyJdCiAgICBDIC0tPiBGWyJIZWFsdGhLaXQgb24gQXBwbGUgcGxhdGZvcm1zIl0KICAgIEMgLS0-IEdbIkhlYWx0aCBDb25uZWN0IG9uIEFuZHJvaWQiXQogICAgQyAtLT4gSFsiTG9jYWwgYW5kIHNpbXVsYXRlZCBzdG9yZSJdCiAgICBFIC0tPiBJWyJCbHVldG9vdGggTEUgc2Vuc29yIHByb2ZpbGVzIl0KICAgIEkgLS0-IEQKICAgIEkgLS0-IEM%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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJBcHBsaWNhdGlvbiJdIC0tPiBCWyJIZWFsdGgiXQogICAgQiAtLT4gQ1siSGVhbHRoU3RvcmUiXQogICAgQiAtLT4gRFsiV29ya291dE1hbmFnZXIiXQogICAgQiAtLT4gRVsiSGVhbHRoU2Vuc29ycyJdCiAgICBDIC0tPiBGWyJIZWFsdGhLaXQgb24gQXBwbGUgcGxhdGZvcm1zIl0KICAgIEMgLS0-IEdbIkhlYWx0aCBDb25uZWN0IG9uIEFuZHJvaWQiXQogICAgQyAtLT4gSFsiTG9jYWwgYW5kIHNpbXVsYXRlZCBzdG9yZSJdCiAgICBFIC0tPiBJWyJCbHVldG9vdGggTEUgc2Vuc29yIHByb2ZpbGVzIl0KICAgIEkgLS0-IEQKICAgIEkgLS0-IEM%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="870" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The strange permission answer preserves privacy
&lt;/h2&gt;

&lt;p&gt;HealthKit does not tell an application whether the user denied read access. A denied read returns no data, which is indistinguishable from an empty store. That prevents an application from learning that a user chose to conceal a sensitive category.&lt;/p&gt;

&lt;p&gt;This produces three rules that can look odd until the privacy boundary is understood:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A successful &lt;code&gt;requestAuthorization(...)&lt;/code&gt; means the user was asked.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getReadAuthorizationStatus(...)&lt;/code&gt; remains &lt;code&gt;UNKNOWN&lt;/code&gt; on iOS.&lt;/li&gt;
&lt;li&gt;There is no &lt;code&gt;hasReadPermission()&lt;/code&gt; method.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Android can report its runtime grant state, so the Android implementation answers that question. The shared API does not force HealthKit to tell a lie for symmetry.&lt;/p&gt;

&lt;p&gt;Do not convert an empty read into “you denied access.” The accurate UI is “no data available,” with an option to open health settings. The simulator has a specific &lt;strong&gt;Grant Write, Deny Read Without Error&lt;/strong&gt; mode because the happy path will not expose this mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  Samples keep their units and identity
&lt;/h2&gt;

&lt;p&gt;The core model covers quantity, category, series, session, sleep, blood pressure, workout, and nutrition samples. A &lt;code&gt;HealthQuantity&lt;/code&gt; has no zero-argument &lt;code&gt;getValue()&lt;/code&gt;. The caller must name a unit:&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;double&lt;/span&gt; &lt;span class="n"&gt;kilograms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getQuantity&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="nc"&gt;HealthUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;KILOGRAM&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That extra word prevents a pound value from silently becoming kilograms in a chart or server payload.&lt;/p&gt;

&lt;p&gt;Queries default to flattening Android heart-rate series into individual &lt;code&gt;QuantitySample&lt;/code&gt; values so iOS and Android present the same common shape. Set &lt;code&gt;flattenSeries&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; when record identity matters, such as deletion. HealthKit has no equivalent series record, so it continues to return scalar samples.&lt;/p&gt;

&lt;p&gt;Sample identifiers belong to the platform and the installation. They are not stable server primary keys. Arbitrary metadata currently round-trips through the local and simulator stores, but not through HealthKit or Health Connect. Keep application correlation identifiers in application storage.&lt;/p&gt;

&lt;p&gt;High-frequency data must be paged. A year of frequent heart-rate samples can approach half a million values, so an unbounded read would be a memory bug disguised as convenience. The default cap is 10,000, and &lt;code&gt;readSamplePage(...)&lt;/code&gt; exposes continuation.&lt;/p&gt;

&lt;h2&gt;
  
  
  An absent value is not zero
&lt;/h2&gt;

&lt;p&gt;An aggregate bucket with no samples returns &lt;code&gt;null&lt;/code&gt;, never zero. No reading and a measured value of zero are different facts.&lt;/p&gt;

&lt;p&gt;Time intervals carry a similar distinction. If a chart is labeled with dates, use calendar intervals with an explicit time zone:&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;HealthInterval&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HealthInterval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;calendarDays&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;userTimeZone&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A fixed 86,400,000 milliseconds is not a local day across daylight-saving changes. Reading the JVM default would also make a server or simulator silently file an evening walk under the wrong date.&lt;/p&gt;

&lt;p&gt;There is one limitation developers need to see clearly: this release computes aggregates from shared raw samples. It does not use HealthKit's source-deduplicating statistics engine. If a phone and watch both record the same walk, the total can count both sources. Filter with &lt;code&gt;addSource(...)&lt;/code&gt; and tell the user which source a number represents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Change subscriptions are a cursor, not a wake-up service
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;HealthSubscription&lt;/code&gt; persists a cursor and restores it across launches. Reuse a stable ID such as &lt;code&gt;steps-v1&lt;/code&gt;, then call &lt;code&gt;drainChanges()&lt;/code&gt; when the application enters the foreground and from background fetch.&lt;/p&gt;

&lt;p&gt;No supported platform wakes a closed application for health changes in this release. &lt;code&gt;isPushDelivery()&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt; everywhere.&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%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBPUyBhcyBIZWFsdGggc3RvcmUKICAgIHBhcnRpY2lwYW50IEFwcCBhcyBBcHBsaWNhdGlvbiBsaWZlY3ljbGUKICAgIHBhcnRpY2lwYW50IFN1YiBhcyBIZWFsdGhTdWJzY3JpcHRpb24KICAgIE9TLT4-T1M6IE5ldyBvciBjaGFuZ2VkIHNhbXBsZQogICAgQXBwLT4-U3ViOiBkcmFpbkNoYW5nZXMoKQogICAgU3ViLT4-T1M6IFJlYWQgYWZ0ZXIgcGVyc2lzdGVkIGN1cnNvcgogICAgT1MtLT4-U3ViOiBDaGFuZ2UgYmF0Y2gKICAgIFN1Yi0tPj5BcHA6IExpc3RlbmVyIG9uIEVEVAogICAgQXBwLT4-U3ViOiBMaXN0ZW5lciBjb21wbGV0ZXMKICAgIFN1Yi0-PlN1YjogQWR2YW5jZSBjdXJzb3I%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%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBPUyBhcyBIZWFsdGggc3RvcmUKICAgIHBhcnRpY2lwYW50IEFwcCBhcyBBcHBsaWNhdGlvbiBsaWZlY3ljbGUKICAgIHBhcnRpY2lwYW50IFN1YiBhcyBIZWFsdGhTdWJzY3JpcHRpb24KICAgIE9TLT4-T1M6IE5ldyBvciBjaGFuZ2VkIHNhbXBsZQogICAgQXBwLT4-U3ViOiBkcmFpbkNoYW5nZXMoKQogICAgU3ViLT4-T1M6IFJlYWQgYWZ0ZXIgcGVyc2lzdGVkIGN1cnNvcgogICAgT1MtLT4-U3ViOiBDaGFuZ2UgYmF0Y2gKICAgIFN1Yi0tPj5BcHA6IExpc3RlbmVyIG9uIEVEVAogICAgQXBwLT4-U3ViOiBMaXN0ZW5lciBjb21wbGV0ZXMKICAgIFN1Yi0-PlN1YjogQWR2YW5jZSBjdXJzb3I%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="657" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Android uses Health Connect change tokens. It can report additions and deletions without replaying an already consumed change. A token can expire, in which case the batch reports that a full resynchronization is required.&lt;/p&gt;

&lt;p&gt;iOS currently uses a timestamp window. It reports additions, not deletions. A backdated reading or a watch sample that syncs after the cursor has advanced can fall behind that window. Applications that require completeness must run a periodic full range query and treat the subscription as a refresh hint. A future anchored HealthKit query can close this gap without changing the public capability model.&lt;/p&gt;

&lt;p&gt;The local and simulator stores persist subscription registration, but they do not synthesize store mutations as change events. Test phone delivery on a phone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workouts are recorded, not magically live
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;WorkoutManager&lt;/code&gt; can create a recorded workout session and accept samples supplied by the application or an attached sensor. &lt;code&gt;isLiveSessionSupported()&lt;/code&gt; and &lt;code&gt;isSensorCollectionSupported()&lt;/code&gt; are both false in this release.&lt;/p&gt;

&lt;p&gt;That distinction matters. The framework does not claim that &lt;code&gt;HKWorkoutSession&lt;/code&gt; or Wear OS Health Services is keeping the process alive. A recorded session lasts as long as the application process and stores only what the application feeds 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;WorkoutConfiguration&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;WorkoutConfiguration&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setActivityType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WorkoutActivityType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CYCLING&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLocationType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WorkoutLocationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OUTDOOR&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;Health&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;getWorkouts&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;startSession&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="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onResult&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;err&lt;/span&gt;&lt;span class="o"&gt;)&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;err&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="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&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="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&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;When a workout ends, its result reports what a platform would not persist. Some sensor values have no single-value Health Connect record, and this API does not pretend otherwise. The complete recorded workout remains available to the application for its own chosen storage or upload path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Eight standard sensor profiles, without the health store
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;com.codename1.health.sensors&lt;/code&gt; covers the adopted Bluetooth SIG profiles for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Heart rate&lt;/li&gt;
&lt;li&gt;Cycling power&lt;/li&gt;
&lt;li&gt;Cycling speed and cadence&lt;/li&gt;
&lt;li&gt;Running speed and cadence&lt;/li&gt;
&lt;li&gt;Health thermometer&lt;/li&gt;
&lt;li&gt;Weight scale&lt;/li&gt;
&lt;li&gt;Blood pressure&lt;/li&gt;
&lt;li&gt;Glucose&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This layer is built on &lt;code&gt;com.codename1.bluetooth.le&lt;/code&gt;. An application can connect to a standard strap, scale, cuff, or meter without touching HealthKit or Health Connect. Sensor-only use therefore needs Bluetooth permissions, not health-store entitlements or a Play health permission review.&lt;/p&gt;

&lt;p&gt;The implementation handles the unglamorous details that usually break field code: cumulative counter differences, timer rollover, IEEE 11073 floating-point values, reconnection, and GATT parsing.&lt;/p&gt;

&lt;p&gt;Writing sensor measurements to the health store is off by default. A strap and the operating system might both record the same heart rate during a workout, and automatic write-through would double-count it. Attach the sensor to the recorded workout, or enable store writes only when that is truly the desired source.&lt;/p&gt;

&lt;p&gt;Several limits are explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RR intervals are decoded, but not emitted as stored HRV samples.&lt;/li&gt;
&lt;li&gt;Live blood-pressure readings work, but phone-store persistence is not implemented.&lt;/li&gt;
&lt;li&gt;Stored glucose-record replay is not implemented.&lt;/li&gt;
&lt;li&gt;Sensor use on watchOS is more limited than phone and desktop support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An explicit &lt;code&gt;TYPE_NOT_SUPPORTED&lt;/code&gt; is less pleasant than a green check mark, but far safer than silently dropping a medical measurement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nutrition is sparse by design
&lt;/h2&gt;

&lt;p&gt;Food contains some measured nutrients, not forty meaningful zeroes. &lt;code&gt;NutritionSample&lt;/code&gt; therefore stores a sparse nutrient map. An absent sodium value remains &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The complete nutrition record currently works in local and simulator stores. On phones, hydration is available on both platforms and dietary energy is available on iOS as ordinary quantity samples. Multi-nutrient phone records are rejected until their native mappings exist.&lt;/p&gt;

&lt;p&gt;Sleep follows a similar rule. The model and local implementation are present, but phone reads are not supported in this release. A phone query fails with &lt;code&gt;TYPE_NOT_SUPPORTED&lt;/code&gt; rather than returning an empty list that could be mistaken for a successful read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build configuration is intentionally strict
&lt;/h2&gt;

&lt;p&gt;An iOS application that reads the store must provide a specific purpose:&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="py"&gt;ios.NSHealthShareUsageDescription&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Reads step count to show weekly activity trends&lt;/span&gt;
&lt;span class="py"&gt;ios.NSHealthUpdateUsageDescription&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Saves workouts you choose to record&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HealthKit capability must also be enabled for the App ID and included in the provisioning profile.&lt;/p&gt;

&lt;p&gt;Android requires an explicit type list and public privacy-policy URL:&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="py"&gt;android.health.read&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;STEPS,HEART_RATE&lt;/span&gt;
&lt;span class="py"&gt;android.health.write&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;STEPS&lt;/span&gt;
&lt;span class="py"&gt;android.health.privacyPolicyUrl&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://example.com/privacy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The build will not insert a generic purpose string. Apple reviews whether that text matches what the application does, and Google Play requires narrow permission declarations. A placeholder would defer a clear build failure into a confusing review rejection.&lt;/p&gt;

&lt;p&gt;Health Connect also raises the Android minimum SDK to 26 and requires a target SDK of at least 30.&lt;/p&gt;

&lt;h2&gt;
  
  
  HIPAA is not a framework switch
&lt;/h2&gt;

&lt;p&gt;This API does not make an application HIPAA compliant.&lt;/p&gt;

&lt;p&gt;HIPAA applies to covered entities and business associates, not to every application containing health data. The relationship and data flow determine the obligation. HHS explicitly notes that an app can fall outside HIPAA after receiving data at an individual's direction, while an app acting for a covered entity may be a business associate. Read the &lt;a href="https://www.hhs.gov/hipaa/for-professionals/special-topics/health-apps/index.html" rel="noopener noreferrer"&gt;HHS mobile health app guidance&lt;/a&gt; and get advice for the specific product and market.&lt;/p&gt;

&lt;p&gt;Being outside HIPAA does not mean being outside health privacy law. The FTC's &lt;a href="https://www.ftc.gov/business-guidance/resources/complying-ftcs-health-breach-notification-rule-0" rel="noopener noreferrer"&gt;Health Breach Notification Rule guidance&lt;/a&gt; explains its reach into many health apps, connected devices, and related services that are not HIPAA covered.&lt;/p&gt;

&lt;p&gt;If a cloud service creates, receives, maintains, or transmits electronic protected health information for a covered entity or business associate, &lt;a href="https://www.hhs.gov/hipaa/for-professionals/special-topics/health-information-technology/cloud-computing/index.html" rel="noopener noreferrer"&gt;HHS cloud guidance&lt;/a&gt; says a business associate agreement may be required even when the provider cannot decrypt the data.&lt;/p&gt;

&lt;p&gt;The framework can enforce some useful boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It never uploads health data.&lt;/li&gt;
&lt;li&gt;The simulator logs types and counts, not sample values.&lt;/li&gt;
&lt;li&gt;Synthetic datasets contain no person's history.&lt;/li&gt;
&lt;li&gt;It refuses missing purpose strings and unsupported writes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your application still owns access control, encryption, audit records, retention, account deletion, breach handling, backend contracts, minimum-necessary collection, consent, and store disclosures. Do not place health samples in unencrypted &lt;code&gt;Storage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Google Play separately requires a health apps declaration and privacy disclosures for health features. The current &lt;a href="https://support.google.com/googleplay/android-developer/answer/16679511" rel="noopener noreferrer"&gt;Health Content and Services policy&lt;/a&gt; treats Health Connect data as sensitive user data. Apple has its own HealthKit privacy and review requirements. Compliance is an application architecture, not a checkbox in a cross-platform API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The simulator tests what a permissive fake cannot
&lt;/h2&gt;

&lt;p&gt;The simulator includes a deterministic seven-day dataset, HealthKit and Health Connect permission modes, provider availability controls, one-shot failures, and the silent read-denial case.&lt;/p&gt;

&lt;p&gt;The release test I care about most is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select &lt;strong&gt;Grant Write, Deny Read Without Error&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Request authorization.&lt;/li&gt;
&lt;li&gt;Read a type that has data in the synthetic dataset.&lt;/li&gt;
&lt;li&gt;Confirm the UI says “no data available.”&lt;/li&gt;
&lt;li&gt;Confirm it does not accuse the user or show a technical error.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That scenario explains why parts of this API look so cautious. Health data punishes false certainty.&lt;/p&gt;

&lt;p&gt;Next in the series: &lt;a href="https://www.codenameone.com/blog/on-device-ai-mcp-loopback/" rel="noopener noreferrer"&gt;on-device AI and MCP over a guarded loopback transport&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Push V3: One Message From Your Server to Every Surface</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Fri, 07 Aug 2026 14:08:16 +0000</pubDate>
      <link>https://dev.to/codenameone/push-v3-one-message-from-your-server-to-every-surface-5gfg</link>
      <guid>https://dev.to/codenameone/push-v3-one-message-from-your-server-to-every-surface-5gfg</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%2F69fwt4ndhdt180bqqte9.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%2F69fwt4ndhdt180bqqte9.jpg" alt="Push V3 connects one typed message to phones, widgets, and live surfaces" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Push notifications should be application infrastructure, not a pile of expiring certificates and provider-specific JSON.&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;This week we merged &lt;a href="https://github.com/codenameone/CodenameOne/pull/5440" rel="noopener noreferrer"&gt;Push V3&lt;/a&gt; into the Codename One core and completed its new cloud implementation. It gives an application a typed message model, managed provider credentials, subscriptions, server-side segments, campaigns, analytics, and a direct path into &lt;a href="https://www.codenameone.com/blog/widgets-live-activities-dynamic-island/" rel="noopener noreferrer"&gt;Surfaces&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is also one thing every existing push developer should do now:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Change the push service URL from &lt;code&gt;https://push.codenameone.com&lt;/code&gt; to &lt;code&gt;https://cloud.codenameone.com&lt;/code&gt; and send a real notification through your existing code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next week we plan to bring down the old push service and direct &lt;code&gt;push.codenameone.com&lt;/code&gt; traffic to the new implementation. The compatibility endpoint accepts the existing request format, so existing code should keep working. It is still a completely new server, and “should” is not a test result. Please test before the cutover while both routes are easy to compare.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Push is the lead story today. Over the next four posts, I will unpack the other changes in this release:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Our own Maven repository: We are moving Codename One releases to &lt;code&gt;repo.codenameone.com&lt;/code&gt;. New projects get the setting automatically next week. Existing projects will need a short POM block before new versions stop appearing on Maven Central.&lt;/li&gt;
&lt;li&gt;
On-device AI and MCP: The core now has portable OCR, vision, language, and LiteRT APIs. The MCP server can inspect and operate a real application on mobile ports through loopback, with a release-build guard because another local process can also reach that socket.&lt;/li&gt;
&lt;li&gt;
Health data: A new API covers HealthKit, Health Connect, workouts, nutrition, eight Bluetooth health sensor profiles, and a deterministic simulator. The design preserves denied reads, missing values, source overlap, and compliance boundaries instead of flattening them into convenient answers.&lt;/li&gt;
&lt;li&gt;
Road-following map routes: &lt;code&gt;Routing.showRoute(...)&lt;/code&gt; can now turn two coordinates into road geometry, distance, duration, legs, and steps. OSRM provides the default test path, while &lt;code&gt;RouteService&lt;/code&gt; keeps production provider choice in application code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test the new push server now
&lt;/h2&gt;

&lt;p&gt;If your server currently sends through the classic endpoint, keep the request exactly as it is and change only the host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-https://push.codenameone.com/push/push
&lt;/span&gt;&lt;span class="gi"&gt;+https://cloud.codenameone.com/push/push
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send to test devices on every platform your application supports. Exercise a visible notification, a data payload, a cold start, and any badge, sound, category, image, or deep-link behavior you use. Compare the result with the old host and &lt;a href="https://github.com/codenameone/CodenameOne/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; if the two disagree.&lt;/p&gt;

&lt;p&gt;The new server contains a classic compatibility layer. Existing applications do not need to adopt the Java V3 client or the new REST API before the hostname switch. That separation matters: validating the new transport is a small operational change, while adopting the V3 model is an application change you can schedule.&lt;/p&gt;

&lt;p&gt;The queue records a provider response for each target. “Accepted” means APNs, FCM, or another provider accepted the request. It does not prove that the operating system displayed the notification, that the user saw it, or that the application opened. The console keeps those states separate because a comforting number with the wrong definition is worse than no number.&lt;/p&gt;

&lt;h2&gt;
  
  
  V3 makes the message a real type
&lt;/h2&gt;

&lt;p&gt;The classic API encoded behavior into numeric push types and positional strings. It worked, but it made provider differences and new destinations increasingly hard to express.&lt;/p&gt;

&lt;p&gt;V3 uses an immutable schema:&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;PushMessage&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PushMessage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&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;"Boarding changed"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Flight CN1 42 now leaves from gate C7"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deepLink&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"myapp://trip/CN142"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tripId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"CN142"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ttlSeconds&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&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 envelope can carry visible content, application data, an image, a deep link, collapse and lifetime rules, provider-specific options, and a &lt;code&gt;surface&lt;/code&gt; command. Incoming messages are parsed before reaching application code, exposed through immutable maps, and rejected when their schema is unsupported.&lt;/p&gt;

&lt;p&gt;V3 also replaces the old &lt;code&gt;PushCallback&lt;/code&gt; contract. Your main application class no longer implements the push interface. You create a &lt;code&gt;PushClient&lt;/code&gt; and give it a &lt;code&gt;PushListener&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Previous API&lt;/th&gt;
&lt;th&gt;V3 API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PushCallback.push(String value)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PushListener.onMessage(PushMessage message)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;registeredForPush(String deviceId)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;onRegistration(PushSubscription subscription)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pushRegistrationError(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;onError(PushError error)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is more than a method rename. The listener receives a parsed &lt;code&gt;PushMessage&lt;/code&gt;, registration returns a subscription object, and errors carry a code plus retry information.&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;private&lt;/span&gt; &lt;span class="nc"&gt;PushClient&lt;/span&gt; &lt;span class="n"&gt;push&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;init&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;push&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PushClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"APP_KEY_FROM_CONSOLE"&lt;/span&gt;&lt;span class="o"&gt;)&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PushListener&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;onMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PushMessage&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;p&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Push: "&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;span class="na"&gt;getTitle&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;onRegistration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PushSubscription&lt;/span&gt; &lt;span class="n"&gt;subscription&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;p&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Registered "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;subscription&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTransportId&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;onError&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PushError&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;p&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCode&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;error&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&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="na"&gt;build&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;start&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;register&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;Create one client in &lt;code&gt;init()&lt;/code&gt;, retain it, and call &lt;code&gt;register()&lt;/code&gt; from &lt;code&gt;start()&lt;/code&gt;. Registration is idempotent. Do not unregister from &lt;code&gt;stop()&lt;/code&gt;, because that removes the subscription rather than pausing it.&lt;/p&gt;

&lt;p&gt;Applications that run their own push infrastructure are not trapped behind the managed service. &lt;code&gt;PushTransport&lt;/code&gt; is a public seam for custom registration and delivery, while &lt;code&gt;PushRegistrationSink&lt;/code&gt; lets an application mirror registration changes to its own backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  One push can update a notification or a Surface
&lt;/h2&gt;

&lt;p&gt;A lock-screen notification is only one destination. Widgets, Live Activities, the Dynamic Island, watch complications, and other &lt;a href="https://www.codenameone.com/blog/widgets-live-activities-dynamic-island/" rel="noopener noreferrer"&gt;Surfaces&lt;/a&gt; also need fresh state.&lt;/p&gt;

&lt;p&gt;V3 reserves a typed &lt;code&gt;surface&lt;/code&gt; object in the same envelope. Native bootstrap code can route a Surface command before the main application UI is running.&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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJQdXNoIFYzIGVudmVsb3BlIl0gLS0-IEJ7IlBheWxvYWQga2luZCJ9CiAgICBCIC0tPiBDWyJWaXNpYmxlIG5vdGlmaWNhdGlvbiJdCiAgICBCIC0tPiBEWyJBcHBsaWNhdGlvbiBkYXRhIl0KICAgIEIgLS0-IEVbIlN1cmZhY2UgY29tbWFuZCJdCiAgICBFIC0tPiBGWyJXaWRnZXQgdGltZWxpbmUiXQogICAgRSAtLT4gR1siTGl2ZSBBY3Rpdml0eSJdCiAgICBFIC0tPiBIWyJEeW5hbWljIElzbGFuZCBvciBjb21wbGljYXRpb24iXQogICAgQyAtLT4gSVsiUHVzaExpc3RlbmVyIG9uIHRoZSBDb2RlbmFtZSBPbmUgRURUIl0KICAgIEQgLS0-IEk%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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJQdXNoIFYzIGVudmVsb3BlIl0gLS0-IEJ7IlBheWxvYWQga2luZCJ9CiAgICBCIC0tPiBDWyJWaXNpYmxlIG5vdGlmaWNhdGlvbiJdCiAgICBCIC0tPiBEWyJBcHBsaWNhdGlvbiBkYXRhIl0KICAgIEIgLS0-IEVbIlN1cmZhY2UgY29tbWFuZCJdCiAgICBFIC0tPiBGWyJXaWRnZXQgdGltZWxpbmUiXQogICAgRSAtLT4gR1siTGl2ZSBBY3Rpdml0eSJdCiAgICBFIC0tPiBIWyJEeW5hbWljIElzbGFuZCBvciBjb21wbGljYXRpb24iXQogICAgQyAtLT4gSVsiUHVzaExpc3RlbmVyIG9uIHRoZSBDb2RlbmFtZSBPbmUgRURUIl0KICAgIEQgLS0-IEk%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1078" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Consider a delivery application that is not running while the customer waits for a courier. A server push can update its Live Activity and Dynamic Island to say “Driver is 2 minutes away” without launching the main Codename One UI. On a platform without that Surface, the same campaign can deliver a normal notification instead.&lt;/p&gt;

&lt;p&gt;The message view still shows whether APNs or another provider accepted each update. That is useful when the Surface changes while no &lt;code&gt;PushListener&lt;/code&gt; is running inside the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  The certificate stops being your server's problem
&lt;/h2&gt;

&lt;p&gt;The old arrangement often made an application team generate a push certificate, place it on its own server, watch its expiry date, and repeat the process. That is fragile infrastructure disguised as setup.&lt;/p&gt;

&lt;p&gt;The new console stores provider credentials for each application and environment. APNs can use a &lt;code&gt;.p8&lt;/code&gt; signing key, which does not have the annual expiry cycle of the old certificate workflow. The push service signs provider requests and isolates credentials from campaign users.&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdbt5zed8mx2ezkh0zbyi.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdbt5zed8mx2ezkh0zbyi.png" alt="Push application settings and provider credentials" width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Credentials are encrypted at rest and treated as write-only secrets in the console. Reading application settings does not return the secret value. This removes certificate hosting from your application server, but it does not remove normal secret hygiene: use a narrowly scoped provider key, rotate it when a team member or system boundary changes, and separate production from development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Segmentation without handing identity to a device
&lt;/h2&gt;

&lt;p&gt;The console separates applications, environments, subscriptions, audiences, messages, campaigns, and analytics.&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp77vx1tqymxz26efcuqi.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp77vx1tqymxz26efcuqi.png" alt="Push applications, environments, and operational state in the console" width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A device can register its provider token through the public client endpoint. It cannot declare an external user identity or attach arbitrary tags to itself. Those operations require the authenticated server API. Otherwise a modified client could simply label itself &lt;code&gt;premium&lt;/code&gt;, &lt;code&gt;administrator&lt;/code&gt;, or &lt;code&gt;patient-high-risk&lt;/code&gt; and enter a segment it did not belong in.&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F14t50u19gl9ad69htr7k.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F14t50u19gl9ad69htr7k.png" alt="A saved push audience built from server-assigned subscription data" width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Saved segments are evaluated on the server against application-scoped subscription data. A segment might select a locale, application version, platform, or a tag assigned by your backend. The audience is resolved when the message is sent, so a corrected tag does not require rebuilding a static mailing list.&lt;/p&gt;

&lt;p&gt;This is segmentation for application behavior, not an advertising profile. Codename One does not sell the subscription data or combine it across customers. The service still has to retain what delivery requires: provider tokens, installation and optional external identifiers, server-assigned tags, message payloads, target status, and provider responses.&lt;/p&gt;

&lt;p&gt;Never place a password, access token, medical result, or other secret in a notification payload. Providers and operating systems participate in delivery, lock screens can expose visible text, and notification data may outlive the screen where you intended to show it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring that answers operational questions
&lt;/h2&gt;

&lt;p&gt;The new message view exposes queued, accepted, failed, and dead targets, including provider error information.&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkyqe0ir73hmcryzo0r2r.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkyqe0ir73hmcryzo0r2r.png" alt="Per-message push state and provider outcomes" width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This makes several operational checks possible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the queue moving?&lt;/li&gt;
&lt;li&gt;Did one provider fail while the others accepted the message?&lt;/li&gt;
&lt;li&gt;Are stale device tokens being removed?&lt;/li&gt;
&lt;li&gt;Did a rate limit delay a large audience?&lt;/li&gt;
&lt;li&gt;Which environment and campaign produced this message?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Analytics are retained for 30 days. They are operational delivery analytics, not proof of attention. Application opens or business outcomes still belong in consent-aware product analytics under your control.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each plan includes
&lt;/h2&gt;

&lt;p&gt;Push sending and managed provider credentials are available on every subscription level, including Free. The plans differ in monthly volume, rate limits, and persistent campaign tooling:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Monthly deliveries per seat&lt;/th&gt;
&lt;th&gt;Requests per minute&lt;/th&gt;
&lt;th&gt;Recipients per minute&lt;/th&gt;
&lt;th&gt;Persistent audiences and campaigns&lt;/th&gt;
&lt;th&gt;Automation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;10,000&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;Enterprise&lt;/td&gt;
&lt;td&gt;10,000,000&lt;/td&gt;
&lt;td&gt;3,000&lt;/td&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Free and Basic applications can send through the same durable provider pipeline. Pro adds saved templates, segments, campaigns, and analytics. Enterprise adds automation and higher operational limits. Quotas are organization and seat aware, so a team can see which allowance a notification run consumes.&lt;/p&gt;

&lt;p&gt;These numbers are the initial policy, not a claim that every application needs a million notifications. Start with a small, explicit audience. A precise notification that helps 200 people is better than a vague blast that trains 200,000 people to turn notifications off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase one of our Maven repository move
&lt;/h2&gt;

&lt;p&gt;We have also merged &lt;a href="https://github.com/codenameone/CodenameOne/pull/5497" rel="noopener noreferrer"&gt;phase one of a move from Maven Central to a Codename One repository on Cloudflare R2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Maven Central has every right to set commercial usage limits and charge for infrastructure. Codename One also has a workload that is difficult to fit inside those limits. One release currently publishes enough duplicated fat-jar content that our dashboard reports 2.12 GB against an 80 MB storage guideline, 19,962 files against 1,000, and 27 releases against 7.&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9gm4mat8rr0kjcu2t8pa.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9gm4mat8rr0kjcu2t8pa.png" alt="Maven Central publishing usage shows Codename One far beyond the soft guidelines" width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Those limits are soft guidelines, and the dashboard offers both open-source adjustments and a commercial plan. We are not leaving because Sonatype is doing something wrong. We are leaving because our weekly, multi-platform release shape is expensive to host there, while we can provide the same Maven layout free to users on infrastructure that fits it better.&lt;/p&gt;

&lt;p&gt;Phase one reduced a measured release payload from 229.5 MB to 76.9 MB. The release pipeline now copies the signed Central staging tree to R2, so it does not perform a second build with potentially different bytes.&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%2FZmxvd2NoYXJ0IExSCiAgICBBWyJKdWx5IDMxPGJyLz5TaHJpbmsgYW5kIGR1YWwgcHVibGlzaCJdIC0tPiBCWyJBdWd1c3QgNzxici8-R2VuZXJhdGVkIFBPTXMgdXNlIFIyIl0KICAgIEIgLS0-IENbIlRocmVlLXdlZWsgb2JzZXJ2YXRpb24gd2luZG93Il0KICAgIEMgLS0-IERbIkF1Z3VzdCAyODxici8-TmV3IHJlbGVhc2VzIG9uIFIyIG9ubHkiXQogICAgQSAtLT4gRVsiTWF2ZW4gQ2VudHJhbCByZW1haW5zIGF1dGhvcml0YXRpdmUiXQogICAgQiAtLT4gRQogICAgRSAtLT4gRlsiRXhpc3RpbmcgQ2VudHJhbCB2ZXJzaW9ucyByZW1haW4gYXZhaWxhYmxlIl0%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%2FZmxvd2NoYXJ0IExSCiAgICBBWyJKdWx5IDMxPGJyLz5TaHJpbmsgYW5kIGR1YWwgcHVibGlzaCJdIC0tPiBCWyJBdWd1c3QgNzxici8-R2VuZXJhdGVkIFBPTXMgdXNlIFIyIl0KICAgIEIgLS0-IENbIlRocmVlLXdlZWsgb2JzZXJ2YXRpb24gd2luZG93Il0KICAgIEMgLS0-IERbIkF1Z3VzdCAyODxici8-TmV3IHJlbGVhc2VzIG9uIFIyIG9ubHkiXQogICAgQSAtLT4gRVsiTWF2ZW4gQ2VudHJhbCByZW1haW5zIGF1dGhvcml0YXRpdmUiXQogICAgQiAtLT4gRQogICAgRSAtLT4gRlsiRXhpc3RpbmcgQ2VudHJhbCB2ZXJzaW9ucyByZW1haW4gYXZhaWxhYmxlIl0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1152" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Existing projects can prepare for post-cutover versions by adding the repository to both Maven resolution paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;repositories&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;repository&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;codenameone&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;https://repo.codenameone.com/maven2&lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/repository&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/repositories&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;pluginRepositories&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;pluginRepository&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;codenameone-plugins&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;https://repo.codenameone.com/maven2&lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/pluginRepository&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/pluginRepositories&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second block matters. Maven resolves build plugins separately from ordinary dependencies. Adding only &lt;code&gt;&amp;lt;repositories&amp;gt;&lt;/code&gt; can leave a future &lt;code&gt;codenameone-maven-plugin&lt;/code&gt; version undiscoverable.&lt;/p&gt;

&lt;p&gt;New projects receive this automatically on August 7. Existing versions remain on Central. The new repository guarantees at least six months of history, while the optimized payload currently fits about 1.6 years at the recent release rate.&lt;/p&gt;

&lt;p&gt;R2 object storage and Cloudflare's edge should improve dependency resolution and remove Central throttling from our release path. We also expect CI and releases to become faster and more stable. Those are expectations we will measure during dual publication, not results we have already proved.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.codenameone.com/blog/maven-central-cloudflare-r2/" rel="noopener noreferrer"&gt;The Maven article publishes on August 4 with the full payload audit, cutover plan, R2 release safeguards, retention policy, and failure modes we are testing during dual publication.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  On-device AI and MCP on every port
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5467" rel="noopener noreferrer"&gt;PR #5467&lt;/a&gt; brings vision, language, and LiteRT inference into the core. The API covers OCR, barcode recognition, face detection, image labels, pose detection, selfie segmentation, document correction, language identification, translation, smart reply, and application-owned &lt;code&gt;.tflite&lt;/code&gt; models.&lt;/p&gt;

&lt;p&gt;The public surface stays portable, but the work remains on the device. Android uses ML Kit for higher-level operations. Apple ports use Vision, Core Image, and Natural Language where they fit. Unsupported ports report an unsupported capability instead of silently uploading input to a cloud fallback.&lt;/p&gt;

&lt;p&gt;OCR is deliberately asynchronous because native conversion and recognition cannot block the Codename One EDT:&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;TextRecognizer&lt;/span&gt; &lt;span class="n"&gt;recognizer&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;TextRecognizer&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;recognizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;process&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;VisionImage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;encoded&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jpegBytes&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;result&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;textArea&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;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getText&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;except&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inference sessions keep an application-owned model loaded across multiple runs. Runtime model downloads can use &lt;code&gt;ModelCache&lt;/code&gt;, which requires HTTPS and verifies the SHA-256 digest before activating the file. A model changes application behavior, so accepting unverified model bytes would be a software supply chain bug.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5472" rel="noopener noreferrer"&gt;PR #5472&lt;/a&gt; takes the existing semantic MCP server beyond JavaSE. On loopback-capable ports, an LLM can read the component tree, find a button by semantic identity, set text, activate an action, and inspect the resulting state in the actual application.&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%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBEZXYgYXMgRGV2ZWxvcGVyIGFuZCBMTE0gY2xpZW50CiAgICBwYXJ0aWNpcGFudCBQb3J0IGFzIERldmljZSBwb3J0IGZvcndhcmQKICAgIHBhcnRpY2lwYW50IE1DUCBhcyAxMjcuMC4wLjEgTUNQIHNlcnZlcgogICAgcGFydGljaXBhbnQgVUkgYXMgQ29kZW5hbWUgT25lIEVEVAogICAgRGV2LT4-UG9ydDogQ29ubmVjdCB0byBkZWJ1ZyBkZXZpY2UKICAgIFBvcnQtPj5NQ1A6IEZvcndhcmQgcG9ydCA4NjQyCiAgICBEZXYtPj5NQ1A6IHVpX3NuYXBzaG90CiAgICBNQ1AtPj5VSTogUmVhZCBzZW1hbnRpYyBjb21wb25lbnQgdHJlZQogICAgVUktLT4-RGV2OiBSb2xlcywgdGV4dCwgc3RhdGUsIGFuZCBhY3Rpb25zCiAgICBEZXYtPj5NQ1A6IEFjdGl2YXRlIHNlbWFudGljIHRhcmdldAogICAgTUNQLT4-VUk6IFBlcmZvcm0gY29tcG9uZW50IGFjdGlvbgogICAgVUktLT4-RGV2OiBVcGRhdGVkIHN0YXRl%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%2Fc2VxdWVuY2VEaWFncmFtCiAgICBwYXJ0aWNpcGFudCBEZXYgYXMgRGV2ZWxvcGVyIGFuZCBMTE0gY2xpZW50CiAgICBwYXJ0aWNpcGFudCBQb3J0IGFzIERldmljZSBwb3J0IGZvcndhcmQKICAgIHBhcnRpY2lwYW50IE1DUCBhcyAxMjcuMC4wLjEgTUNQIHNlcnZlcgogICAgcGFydGljaXBhbnQgVUkgYXMgQ29kZW5hbWUgT25lIEVEVAogICAgRGV2LT4-UG9ydDogQ29ubmVjdCB0byBkZWJ1ZyBkZXZpY2UKICAgIFBvcnQtPj5NQ1A6IEZvcndhcmQgcG9ydCA4NjQyCiAgICBEZXYtPj5NQ1A6IHVpX3NuYXBzaG90CiAgICBNQ1AtPj5VSTogUmVhZCBzZW1hbnRpYyBjb21wb25lbnQgdHJlZQogICAgVUktLT4-RGV2OiBSb2xlcywgdGV4dCwgc3RhdGUsIGFuZCBhY3Rpb25zCiAgICBEZXYtPj5NQ1A6IEFjdGl2YXRlIHNlbWFudGljIHRhcmdldAogICAgTUNQLT4-VUk6IFBlcmZvcm0gY29tcG9uZW50IGFjdGlvbgogICAgVUktLT4-RGV2OiBVcGRhdGVkIHN0YXRl%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="975" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This replaces coordinate guessing with application semantics. It also creates a control channel. Binding to &lt;code&gt;127.0.0.1&lt;/code&gt; prevents accidental exposure to the local network, but it does not authenticate other processes on the device or workstation.&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;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&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;isDebuggableBuild&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;MCP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startSocketServer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8642&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;MCP refuses to start in a release build by default. An explicit override exists for controlled test labs, but it should not become a convenience flag in a consumer application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.codenameone.com/blog/on-device-ai-mcp-loopback/" rel="noopener noreferrer"&gt;The AI and MCP article publishes on August 2 with the capability matrix, portable inference model, semantic debugging loop, and the reasons loopback still needs a release-build gate.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Health data without fake certainty
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5475" rel="noopener noreferrer"&gt;PR #5475&lt;/a&gt; adds a first-class API for HealthKit, Health Connect, recorded workouts, sparse nutrition data, deterministic simulation, and eight adopted Bluetooth health sensor profiles.&lt;/p&gt;

&lt;p&gt;The public API is divided at the boundaries an application needs to reason about:&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;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;com.codename1.health&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stores, permissions, samples, queries, aggregates, sources, and change cursors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;com.codename1.health.workout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recorded workout sessions, events, configuration, and collected samples&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;com.codename1.health.sensors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Live standard Bluetooth health devices without requiring a phone health store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;com.codename1.health.nutrition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sparse nutrient records where an absent value remains absent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Simulator, desktop, and JavaScript builds return &lt;code&gt;LOCAL_ONLY&lt;/code&gt;. That is a supported store with reads and writes, not a missing provider. Only Android provider failures should send the user to provider setup:&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;Health&lt;/span&gt; &lt;span class="n"&gt;health&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Health&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="nc"&gt;HealthAvailability&lt;/span&gt; &lt;span class="n"&gt;availability&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAvailability&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;availability&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;HealthAvailability&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PROVIDER_NOT_INSTALLED&lt;/span&gt;
        &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;availability&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;HealthAvailability&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PROVIDER_UPDATE_REQUIRED&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;openProviderSetup&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="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;availability&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;HealthAvailability&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NOT_SUPPORTED&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="nc"&gt;HealthStore&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStore&lt;/span&gt;&lt;span class="o"&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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJBcHBsaWNhdGlvbiJdIC0tPiBCWyJIZWFsdGgiXQogICAgQiAtLT4gQ1siSGVhbHRoU3RvcmUiXQogICAgQiAtLT4gRFsiV29ya291dE1hbmFnZXIiXQogICAgQiAtLT4gRVsiSGVhbHRoU2Vuc29ycyJdCiAgICBDIC0tPiBGWyJIZWFsdGhLaXQiXQogICAgQyAtLT4gR1siSGVhbHRoIENvbm5lY3QiXQogICAgQyAtLT4gSFsiTG9jYWwgYW5kIHNpbXVsYXRlZCBzdG9yZSJdCiAgICBFIC0tPiBJWyJFaWdodCBCbHVldG9vdGggTEUgcHJvZmlsZXMiXQogICAgSSAtLT4gRAogICAgSSAtLT4gQw%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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJBcHBsaWNhdGlvbiJdIC0tPiBCWyJIZWFsdGgiXQogICAgQiAtLT4gQ1siSGVhbHRoU3RvcmUiXQogICAgQiAtLT4gRFsiV29ya291dE1hbmFnZXIiXQogICAgQiAtLT4gRVsiSGVhbHRoU2Vuc29ycyJdCiAgICBDIC0tPiBGWyJIZWFsdGhLaXQiXQogICAgQyAtLT4gR1siSGVhbHRoIENvbm5lY3QiXQogICAgQyAtLT4gSFsiTG9jYWwgYW5kIHNpbXVsYXRlZCBzdG9yZSJdCiAgICBFIC0tPiBJWyJFaWdodCBCbHVldG9vdGggTEUgcHJvZmlsZXMiXQogICAgSSAtLT4gRAogICAgSSAtLT4gQw%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="654" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some of the API looks cautious because the platform contracts are cautious. HealthKit does not reveal whether a user denied read access. A completed authorization sheet means the user was asked, not that the application can read the category. The shared API therefore has no &lt;code&gt;hasReadPermission()&lt;/code&gt; method that would lie on iOS.&lt;/p&gt;

&lt;p&gt;The API preserves these distinctions throughout the model. An empty aggregate returns &lt;code&gt;null&lt;/code&gt;, not zero. Calendar-day buckets require a time zone. Phone and watch sources remain distinguishable because silently adding overlapping samples can double-count a walk. Unsupported phone mappings fail with &lt;code&gt;TYPE_NOT_SUPPORTED&lt;/code&gt; instead of returning an empty collection that looks successful.&lt;/p&gt;

&lt;p&gt;The simulator includes a mode that grants writes while denying reads without an error. That lets you test the UI mistake that a permissive fake would miss: accusing a user of denying access when the only accurate statement is “no data available.”&lt;/p&gt;

&lt;p&gt;This API does not make an application HIPAA compliant. It never uploads health data, and it can enforce specific purpose strings and reject unsupported writes. The application still owns access control, encryption, audit records, retention, consent, breach handling, backend contracts, and store disclosures.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.codenameone.com/blog/health-api-false-certainty/" rel="noopener noreferrer"&gt;The Health article publishes on August 1 with the platform matrix, authorization trap, sample model, change cursors, workouts, Bluetooth sensors, simulator failure modes, build configuration, and HIPAA boundary.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A polyline is not a route
&lt;/h2&gt;

&lt;p&gt;A map polyline joins coordinates that already exist. It cannot discover the roads, travel time, maneuvers, or alternate paths between them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5480" rel="noopener noreferrer"&gt;PR #5480&lt;/a&gt; adds &lt;code&gt;com.codename1.maps.routing&lt;/code&gt;. The smallest useful path is two coordinates:&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;MapView&lt;/span&gt; &lt;span class="n"&gt;map&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;MapView&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;Routing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;showRoute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LatLng&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;38.8977&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;77.0365&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LatLng&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;38.8894&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;77.0352&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 call returns immediately. The active &lt;code&gt;RouteService&lt;/code&gt; finds a route, then the API draws its geometry and frames the map on the Codename One EDT.&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%2FZmxvd2NoYXJ0IExSCiAgICBBWyJPcmlnaW4sIGRlc3RpbmF0aW9uLCBhbmQgd2F5cG9pbnRzIl0gLS0-IEJbIlJvdXRlUmVxdWVzdCJdCiAgICBCIC0tPiBDWyJSb3V0ZVNlcnZpY2UiXQogICAgQyAtLT4gRFsiUm9hZCBuZXR3b3JrIGNhbGN1bGF0aW9uIl0KICAgIEQgLS0-IEVbIlJvdXRlIGFsdGVybmF0aXZlcyJdCiAgICBFIC0tPiBGWyJHZW9tZXRyeSBhbmQgYm91bmRzIl0KICAgIEUgLS0-IEdbIkRpc3RhbmNlLCBkdXJhdGlvbiwgbGVncywgYW5kIHN0ZXBzIl0KICAgIEYgLS0-IEhbIk1hcFN1cmZhY2UiXQogICAgRyAtLT4gSVsiQXBwbGljYXRpb24gVUkiXQ%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%2FZmxvd2NoYXJ0IExSCiAgICBBWyJPcmlnaW4sIGRlc3RpbmF0aW9uLCBhbmQgd2F5cG9pbnRzIl0gLS0-IEJbIlJvdXRlUmVxdWVzdCJdCiAgICBCIC0tPiBDWyJSb3V0ZVNlcnZpY2UiXQogICAgQyAtLT4gRFsiUm9hZCBuZXR3b3JrIGNhbGN1bGF0aW9uIl0KICAgIEQgLS0-IEVbIlJvdXRlIGFsdGVybmF0aXZlcyJdCiAgICBFIC0tPiBGWyJHZW9tZXRyeSBhbmQgYm91bmRzIl0KICAgIEUgLS0-IEdbIkRpc3RhbmNlLCBkdXJhdGlvbiwgbGVncywgYW5kIHN0ZXBzIl0KICAgIEYgLS0-IEhbIk1hcFN1cmZhY2UiXQogICAgRyAtLT4gSVsiQXBwbGljYXRpb24gVUkiXQ%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1741" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The default service is OSRM, so the first driving route needs no provider signup. It points to the public OSRM demonstration server, which has no production SLA and uses a car profile. A &lt;code&gt;WALKING&lt;/code&gt; request does not turn that graph into a pedestrian route.&lt;/p&gt;

&lt;p&gt;Production applications can point &lt;code&gt;OsrmRouteService&lt;/code&gt; at their own server or install another provider:&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;Routing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setService&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;OsrmRouteService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"https://routing.example.com"&lt;/span&gt;
&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application consumes portable &lt;code&gt;Route&lt;/code&gt; objects rather than provider JSON. Those objects carry alternatives, distance, duration, bounds, geometry, legs, step instructions, maneuver locations, and provider metadata.&lt;/p&gt;

&lt;p&gt;This is routing, not turn-by-turn navigation. The release does not claim rerouting, traffic prediction, offline map packages, or voice guidance. Those features need location updates, lifecycle state, and provider-specific rules.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.codenameone.com/blog/road-following-map-routing/" rel="noopener noreferrer"&gt;The routing article publishes on August 3 with custom route styling, ETA handling, encoded polyline support, OSRM limits, travel modes, provider replacement, and the boundary between a route result and navigation.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tomorrow I will start taking these changes one at a time with the new Health API. For today, please send a real notification through &lt;code&gt;cloud.codenameone.com&lt;/code&gt;. This is the one week when you can compare the old and new push implementations side by side. If anything behaves differently, &lt;a href="https://github.com/codenameone/CodenameOne/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; before we move the traffic next week.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Compact Strings Cut Character Storage in Half</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Wed, 05 Aug 2026 15:10:15 +0000</pubDate>
      <link>https://dev.to/codenameone/compact-strings-cut-character-storage-in-half-542h</link>
      <guid>https://dev.to/codenameone/compact-strings-cut-character-storage-in-half-542h</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%2F5uns3l7tr5q7h0q7d30u.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%2F5uns3l7tr5q7h0q7d30u.jpg" alt="Compact Strings Cut Character Storage in Half" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5421" rel="noopener noreferrer"&gt;PR #5421&lt;/a&gt; adds compact strings to ParparVM. Strings that fit in Latin-1 now use a &lt;code&gt;byte[]&lt;/code&gt;; strings that need wider code units continue to use &lt;code&gt;char[]&lt;/code&gt;.&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;ParparVM previously stored every Java &lt;code&gt;String&lt;/code&gt; in a &lt;code&gt;char[]&lt;/code&gt;. Class names, JSON keys, URLs, numbers, log messages, and much Western European text therefore used two bytes per code unit when one byte was enough.&lt;/p&gt;

&lt;p&gt;The implementation follows the basic approach in &lt;a href="https://openjdk.org/jeps/254" rel="noopener noreferrer"&gt;JEP 254&lt;/a&gt; for modern HotSpot. ParparVM also had to preserve fused allocation without adding another pointer to every string.&lt;/p&gt;

&lt;h2&gt;
  
  
  One field, two possible array types
&lt;/h2&gt;

&lt;p&gt;Using one field for each array type would add a second reference to every string:&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="c1"&gt;// We did not do this.&lt;/span&gt;
&lt;span class="kd"&gt;private&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;latin1Value&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;char&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;utf16Value&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every string would pay for two references even though one is always &lt;code&gt;null&lt;/code&gt;. On a heap full of short strings, that fixed cost can erase a meaningful part of the saving.&lt;/p&gt;

&lt;p&gt;The actual shape keeps one backing reference:&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;@Fused&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Holds either byte[] for Latin-1 or char[] for UTF-16.&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;value&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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;offset&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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&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;char&lt;/span&gt; &lt;span class="nf"&gt;charInternal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;v&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="kt"&gt;byte&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="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;)&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;v&lt;/span&gt;&lt;span class="o"&gt;)[&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xff&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="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;[])&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;)[&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&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;The concrete array type is the encoding marker. There is no second array reference and no separate &lt;code&gt;coder&lt;/code&gt; field.&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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJuZXcgU3RyaW5nIGNvbnRlbnQiXSAtLT4gQnsiRXZlcnkgY29kZSB1bml0XG5maXRzIGluIExhdGluLTE_In0KICAgIEIgLS0-fFllc3wgQ1siU3RyaW5nLnZhbHVlIOKGkiBieXRlW11cbjEgYnl0ZSBwZXIgY29kZSB1bml0Il0KICAgIEIgLS0-fE5vfCBEWyJTdHJpbmcudmFsdWUg4oaSIGNoYXJbXVxuMiBieXRlcyBwZXIgY29kZSB1bml0Il0KICAgIEMgLS0-IEVbIlNhbWUgU3RyaW5nIEFQSSJdCiAgICBEIC0tPiBFCiAgICBFIC0tPiBGWyJjaGFyQXQsIGNvbXBhcmUsIGhhc2gsXG5zZWFyY2gsIG5hdGl2ZSBpbnRlcm9wIl0%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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJuZXcgU3RyaW5nIGNvbnRlbnQiXSAtLT4gQnsiRXZlcnkgY29kZSB1bml0XG5maXRzIGluIExhdGluLTE_In0KICAgIEIgLS0-fFllc3wgQ1siU3RyaW5nLnZhbHVlIOKGkiBieXRlW11cbjEgYnl0ZSBwZXIgY29kZSB1bml0Il0KICAgIEIgLS0-fE5vfCBEWyJTdHJpbmcudmFsdWUg4oaSIGNoYXJbXVxuMiBieXRlcyBwZXIgY29kZSB1bml0Il0KICAgIEMgLS0-IEVbIlNhbWUgU3RyaW5nIEFQSSJdCiAgICBEIC0tPiBFCiAgICBFIC0tPiBGWyJjaGFyQXQsIGNvbXBhcmUsIGhhc2gsXG5zZWFyY2gsIG5hdGl2ZSBpbnRlcm9wIl0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="484" height="692"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This does not halve the complete memory cost of every &lt;code&gt;String&lt;/code&gt;. Object headers, the one backing reference, and the other fields remain. It halves the character-array storage when the value fits in Latin-1. Short strings save less in absolute terms; long Latin-1 strings approach the full 50 percent backing-store reduction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fused allocation made the layout harder
&lt;/h2&gt;

&lt;p&gt;ParparVM can mark a class with &lt;code&gt;@Fused&lt;/code&gt;. When the translator recognizes a suitable constructor, it allocates the owner and its child array in one BiBOP block. A &lt;code&gt;String&lt;/code&gt; and its backing array can therefore need one allocation instead of two.&lt;/p&gt;

&lt;p&gt;Before compact strings, the translator knew that &lt;code&gt;String.value&lt;/code&gt; was a &lt;code&gt;char[]&lt;/code&gt;. Changing the field to &lt;code&gt;Object&lt;/code&gt; hid the child array type from the ordinary descriptor check. Dropping fusion would have kept the code simple, but it would have traded away an existing allocation and locality optimization on one of the most frequently created objects in a Java application.&lt;/p&gt;

&lt;p&gt;The translator now handles &lt;code&gt;String.value&lt;/code&gt; as a narrow special case. It accepts only a freshly created &lt;code&gt;byte[]&lt;/code&gt; or &lt;code&gt;char[]&lt;/code&gt;, sizes the fused block for that concrete element type, and installs that array in the same &lt;code&gt;value&lt;/code&gt; slot.&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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJTZXBhcmF0ZSBsYXlvdXQ6IGFsbG9jYXRlIHRoZSBTdHJpbmcgb2JqZWN0Il0gLS0-IEJbIkFsbG9jYXRlIGl0cyBieXRlW10gb3IgY2hhcltdIGJhY2tpbmcgYXJyYXkiXQogICAgQiAtLT4gQ1siVHJhbnNsYXRvciBwcm92ZXMgdGhlIGNvbnN0cnVjdG9yIGFuZCBhcnJheSB0eXBlIl0KICAgIEMgLS0-IERbIkZ1c2VkIGxheW91dDogU3RyaW5nIGZpZWxkcyBhbmQgYmFja2luZyBzdG9yYWdlIHNoYXJlIG9uZSBCaUJPUCBibG9jayJd%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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJTZXBhcmF0ZSBsYXlvdXQ6IGFsbG9jYXRlIHRoZSBTdHJpbmcgb2JqZWN0Il0gLS0-IEJbIkFsbG9jYXRlIGl0cyBieXRlW10gb3IgY2hhcltdIGJhY2tpbmcgYXJyYXkiXQogICAgQiAtLT4gQ1siVHJhbnNsYXRvciBwcm92ZXMgdGhlIGNvbnN0cnVjdG9yIGFuZCBhcnJheSB0eXBlIl0KICAgIEMgLS0-IERbIkZ1c2VkIGxheW91dDogU3RyaW5nIGZpZWxkcyBhbmQgYmFja2luZyBzdG9yYWdlIHNoYXJlIG9uZSBCaUJPUCBibG9jayJd%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="276" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a garbage-collector constraint here too. The backing array must be initialized before the fused object becomes visible to the concurrent collector. A half-built string is not merely a performance bug; it can become a bad heap edge.&lt;/p&gt;

&lt;h2&gt;
  
  
  String operations keep the compact representation
&lt;/h2&gt;

&lt;p&gt;Native string operations identify the backing array once and keep that check outside their character loops. They do not convert compact strings to &lt;code&gt;char[]&lt;/code&gt; for each operation.&lt;/p&gt;

&lt;p&gt;Numeric conversion has a direct Latin-1 path because decimal digits are known to fit. Common concatenation shapes with two to five string parts are lowered to helpers that first determine the result representation, allocate the result once, and fill it 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="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="s"&gt;"users/"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/settings"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When every part is already Latin-1, that result stays compact. If any part needs a wider code unit, the result uses &lt;code&gt;char[]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Hand-written native code needed attention as well. Code that previously cast &lt;code&gt;String.value&lt;/code&gt; directly to &lt;code&gt;char[]&lt;/code&gt; had to go through coder-aware access. That audit matters because Java-level tests can pass while an old native cast quietly reads the wrong array layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unicode still takes the correct path
&lt;/h2&gt;

&lt;p&gt;Latin-1 covers code points from 0 through 255. It does not cover Hebrew, Arabic, most Asian scripts, emoji, or the rest of Unicode.&lt;/p&gt;

&lt;p&gt;Those strings continue to use &lt;code&gt;char[]&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;String&lt;/span&gt; &lt;span class="n"&gt;compact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Résumé 2026"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Latin-1: byte[] backing&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;wide&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="c1"&gt;// outside Latin-1: char[] backing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The representation changes storage inside the VM, not the supported character set. &lt;code&gt;length()&lt;/code&gt;, &lt;code&gt;charAt()&lt;/code&gt;, comparison, hashing, substring operations, encoding, and native interop retain their Java behavior.&lt;/p&gt;

&lt;p&gt;Application code does not change. Latin-1 strings use less backing storage and remain compatible with fused allocation, native string operations, and the concurrent collector.&lt;/p&gt;

&lt;p&gt;Start the release series with &lt;a href="https://www.codenameone.com/blog/javascript-free-open-source/" rel="noopener noreferrer"&gt;the free and open-source JavaScript port&lt;/a&gt;, or read about &lt;a href="https://www.codenameone.com/blog/calendar-is-not-add-event/" rel="noopener noreferrer"&gt;calendar synchronization&lt;/a&gt;, &lt;a href="https://www.codenameone.com/blog/bluetooth-beyond-ble/" rel="noopener noreferrer"&gt;Bluetooth support&lt;/a&gt;, &lt;a href="https://www.codenameone.com/blog/text-input-without-native-overlay/" rel="noopener noreferrer"&gt;pure Codename One text editing&lt;/a&gt;, and &lt;a href="https://www.codenameone.com/blog/rich-text-without-webview/" rel="noopener noreferrer"&gt;lightweight rich text&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>A Lightweight Rich Text Component Without a Web View</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Tue, 04 Aug 2026 15:30:13 +0000</pubDate>
      <link>https://dev.to/codenameone/a-lightweight-rich-text-component-without-a-web-view-3ch0</link>
      <guid>https://dev.to/codenameone/a-lightweight-rich-text-component-without-a-web-view-3ch0</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%2Fswktrsmx7sl9a76yo1tp.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%2Fswktrsmx7sl9a76yo1tp.jpg" alt="A Lightweight Rich Text Component Without a Web View" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5421" rel="noopener noreferrer"&gt;PR #5421&lt;/a&gt; adds &lt;code&gt;RichTextComponent&lt;/code&gt;, a read-only component for formatted application text. It supports headings, inline styles, lists, links, and images without embedding a web view.&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;A &lt;code&gt;SpanLabel&lt;/code&gt; applies one style to wrapped text. A &lt;code&gt;BrowserComponent&lt;/code&gt; renders a complete web page. &lt;code&gt;RichTextComponent&lt;/code&gt; covers formatted document content between those two cases and participates in ordinary Codename One layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rich text inside a scrollable container
&lt;/h2&gt;

&lt;p&gt;A common screen mixes formatted text with buttons, images, forms, and other Codename One components inside one scrollable container. A &lt;code&gt;BrowserComponent&lt;/code&gt; is a poor fit for that layout because it owns a rectangular native surface and its own page viewport. The browser's document height does not naturally become the height of a child inside the parent Codename One layout.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RichTextComponent&lt;/code&gt; measures wrapped runs for the width it receives and reports the corresponding height. In the default &lt;code&gt;SizeMode.SHRINK&lt;/code&gt;, it behaves like a &lt;code&gt;SpanLabel&lt;/code&gt;: the parent container scrolls the rich text together with the surrounding components. &lt;code&gt;SizeMode.SCROLL&lt;/code&gt; is available when the rich text should keep an assigned height and scroll its own content.&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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJIVE1MLCBNYXJrZG93biwgQXNjaWlEb2MsIFJURiwgb3Igc3R5bGVkIEphdmEgcnVucyJdIC0tPiBCWyJSaWNoIGRvY3VtZW50IG1vZGVsIl0KICAgIEIgLS0-IENbIlJpY2hSdW5QYWludGVyIl0KICAgIEMgLS0-IERbIlJpY2hUZXh0QXJlYSBlZGl0b3Igb3IgUmljaFRleHRDb21wb25lbnQgdmlld2VyIl0KICAgIEQgLS0-IEVbIkNvZGVuYW1lIE9uZSBsYXlvdXQgYW5kIEdyYXBoaWNzIl0%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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJIVE1MLCBNYXJrZG93biwgQXNjaWlEb2MsIFJURiwgb3Igc3R5bGVkIEphdmEgcnVucyJdIC0tPiBCWyJSaWNoIGRvY3VtZW50IG1vZGVsIl0KICAgIEIgLS0-IENbIlJpY2hSdW5QYWludGVyIl0KICAgIEMgLS0-IERbIlJpY2hUZXh0QXJlYSBlZGl0b3Igb3IgUmljaFRleHRDb21wb25lbnQgdmlld2VyIl0KICAgIEQgLS0-IEVbIkNvZGVuYW1lIE9uZSBsYXlvdXQgYW5kIEdyYXBoaWNzIl0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="276" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The read-only view and editor agree on paragraph attributes, inline styles, links, image runs, and wrapping because they do not maintain competing renderers.&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6kjlnyqec0ao4kxhleu5.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6kjlnyqec0ao4kxhleu5.png" alt="Rich text with headings, emphasis, and a list" width="320" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Supply the format you already have
&lt;/h2&gt;

&lt;p&gt;HTML is not the only 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;RichTextComponent&lt;/span&gt; &lt;span class="n"&gt;view&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;RichTextComponent&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setMarkdown&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"# Trip summary\n\n"&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"Departs **09:40**, arrives *11:15*. "&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"See the [itinerary](app://itinerary).\n\n"&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"- Window seat\n"&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"- Carry-on only"&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="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;setContent(...)&lt;/code&gt; accepts &lt;code&gt;RichTextFormat.HTML&lt;/code&gt;, &lt;code&gt;MARKDOWN&lt;/code&gt;, &lt;code&gt;ASCIIDOC&lt;/code&gt;, or &lt;code&gt;RTF&lt;/code&gt;. The model covers headings, emphasis, inline code, links, images, lists, quotes, literal blocks, paragraph alignment, indentation, foreground colors, and highlights.&lt;/p&gt;

&lt;p&gt;You can also assemble content without markup:&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;RichTextComponent&lt;/span&gt; &lt;span class="n"&gt;status&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;RichTextComponent&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Status: "&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TextStyle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DEFAULT&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"confirmed"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
              &lt;span class="nc"&gt;TextStyle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DEFAULT&lt;/span&gt;
                      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withBold&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;withForeColor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x1a7f37&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This path is useful when the content already arrives as structured application data. It avoids generating markup only to parse it again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links are application actions
&lt;/h2&gt;

&lt;p&gt;A link target does not automatically leave the application:&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;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addLinkListener&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="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;execute&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;String&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;.&lt;/span&gt;&lt;span class="na"&gt;getSource&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The event source is the target string. An &lt;code&gt;https:&lt;/code&gt; URL can open a browser. An &lt;code&gt;app:&lt;/code&gt; target can navigate to another form. The application owns the policy.&lt;/p&gt;

&lt;p&gt;Images follow the same rule:&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;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setImageResolver&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;imageCache&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="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component does not create a second network stack or choose a cache lifetime. The resolver returns the image for a source string, or &lt;code&gt;null&lt;/code&gt; for a placeholder. That keeps loading, authentication, and caching in application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The editor supports the same formats in both directions
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;RichTextArea&lt;/code&gt; now imports and exports HTML, Markdown, AsciiDoc, and a practical RTF subset through direct model adapters. Markdown and AsciiDoc do not convert through HTML first.&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;RichTextArea&lt;/span&gt; &lt;span class="n"&gt;editor&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;RichTextArea&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setContent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"# Release notes\n\nThis is **ready**."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;RichTextFormat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MARKDOWN&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;insertContent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"{\\rtf1\\ansi {\\i pasted notes}}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;RichTextFormat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RTF&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;insertContent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"== Details\n\n* Portable\n* Lightweight"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;RichTextFormat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ASCIIDOC&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Editing and reading the same format returns canonical output for that format. It preserves supported meaning, not the original whitespace, tag aliases, or attribute order.&lt;/p&gt;

&lt;p&gt;Rich clipboard data uses the same negotiation. A copy can publish plain text, HTML, RTF, Markdown, and AsciiDoc together. The receiving component chooses the richest format it understands. Ports with only a plain-text system clipboard still keep the richer payload for transfers inside the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  The importer handles document markup, not web pages
&lt;/h2&gt;

&lt;p&gt;The HTML importer does not execute scripts. It supports the markup that maps to the lightweight document model. It does not implement a CSS cascade, arbitrary DOM layout, forms, video, or embedded JavaScript.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Content and layout&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;Label&lt;/code&gt; / &lt;code&gt;SpanLabel&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;&lt;code&gt;RichTextComponent&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;BrowserComponent&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simple text&lt;/td&gt;
&lt;td&gt;✓ Best fit&lt;/td&gt;
&lt;td&gt;Works, but unnecessary&lt;/td&gt;
&lt;td&gt;Works, but adds a browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mixed with other Codename One content&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;Separate browser surface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Child of a scrollable Codename One container&lt;/td&gt;
&lt;td&gt;✓ Participates in parent layout&lt;/td&gt;
&lt;td&gt;✓ Measures its content and participates in parent layout&lt;/td&gt;
&lt;td&gt;Owns a separate viewport and scrolling surface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Simple HTML or formatted document content&lt;/td&gt;
&lt;td&gt;Convert to plain or uniformly styled text&lt;/td&gt;
&lt;td&gt;✓ HTML, Markdown, AsciiDoc, and RTF&lt;/td&gt;
&lt;td&gt;✓ HTML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex HTML, CSS, forms, video, or JavaScript&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;Use &lt;code&gt;RichTextArea&lt;/code&gt; instead when the formatted document must be editable.&lt;/p&gt;

&lt;p&gt;Validate link targets and image sources as application data. A script-free importer prevents script execution; it does not make an untrusted URL safe to open.&lt;/p&gt;

&lt;h2&gt;
  
  
  The viewer and editor use the same painter
&lt;/h2&gt;

&lt;p&gt;The June editor implementation used a web view by default. The editor and a read-only Codename One rendering could therefore disagree on wrapping, font metrics, or supported markup.&lt;/p&gt;

&lt;p&gt;The new path keeps editing and display on the same model and &lt;code&gt;RichRunPainter&lt;/code&gt;. A note can move from &lt;code&gt;RichTextArea&lt;/code&gt; to &lt;code&gt;RichTextComponent&lt;/code&gt; without crossing a browser serialization and rendering boundary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.codenameone.com/blog/text-input-without-native-overlay/" rel="noopener noreferrer"&gt;Yesterday's post&lt;/a&gt; described the operating system sending text operations into a portable document. &lt;code&gt;RichTextComponent&lt;/code&gt; displays the same document model with the same painter and no editing session.&lt;/p&gt;

&lt;p&gt;Try it where you currently use a &lt;code&gt;BrowserComponent&lt;/code&gt; only to show formatted application text. Keep the browser for pages that are actually web content.&lt;/p&gt;

&lt;p&gt;The last post in this week's series covers &lt;a href="https://www.codenameone.com/blog/compact-strings-parparvm/" rel="noopener noreferrer"&gt;compact strings in ParparVM&lt;/a&gt;. You can also start with &lt;a href="https://www.codenameone.com/blog/javascript-free-open-source/" rel="noopener noreferrer"&gt;free and local JavaScript builds&lt;/a&gt;, &lt;a href="https://www.codenameone.com/blog/calendar-is-not-add-event/" rel="noopener noreferrer"&gt;calendar synchronization&lt;/a&gt;, &lt;a href="https://www.codenameone.com/blog/bluetooth-beyond-ble/" rel="noopener noreferrer"&gt;Bluetooth support&lt;/a&gt;, or &lt;a href="https://www.codenameone.com/blog/text-input-without-native-overlay/" rel="noopener noreferrer"&gt;pure Codename One text editing&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Pure Codename One Text Editing Without Native Overlays</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Mon, 03 Aug 2026 15:39:51 +0000</pubDate>
      <link>https://dev.to/codenameone/pure-codename-one-text-editing-without-native-overlays-2i9l</link>
      <guid>https://dev.to/codenameone/pure-codename-one-text-editing-without-native-overlays-2i9l</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%2Fqryoqve572pjow1kvmd5.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%2Fqryoqve572pjow1kvmd5.jpg" alt="Pure Codename One Text Editing Without Native Overlays" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5386" rel="noopener noreferrer"&gt;PR #5386&lt;/a&gt; adds a pure Codename One text-editing path. &lt;code&gt;EditField&lt;/code&gt;, &lt;code&gt;RichTextArea&lt;/code&gt;, and &lt;code&gt;CodeEditor&lt;/code&gt; can now keep their document, selection, and painting inside the lightweight UI while each port supplies keyboard and input-method events.&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;Text input must handle virtual keyboards, hardware keys, autocorrect, dictation, marked text from an input method editor, bidirectional text, selection, clipboard formats, and accessibility geometry.&lt;/p&gt;

&lt;p&gt;Codename One traditionally delegates that work to a native platform field placed over the lightweight component during editing. The overlay remains the default for &lt;code&gt;TextField&lt;/code&gt; and &lt;code&gt;TextArea&lt;/code&gt;. It can create a small visual jump, and it cannot participate in lightweight painting for syntax highlights, rich runs, masks, inline images, or a custom selection model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The port sends text operations instead of key codes
&lt;/h2&gt;

&lt;p&gt;A soft keyboard does not type keys. It commits words, replaces a marked composition range, deletes text around the caret, and changes selection. Dictation may insert a sentence without producing one key event.&lt;/p&gt;

&lt;p&gt;The new &lt;code&gt;TextInputClient&lt;/code&gt; contract models those operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;commitText(...)&lt;/code&gt; inserts final text.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;setComposingText(...)&lt;/code&gt; replaces the active marked-text range.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;finishComposing()&lt;/code&gt; accepts that range.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deleteSurroundingText(...)&lt;/code&gt; implements virtual-keyboard deletion.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onKeyCommand(...)&lt;/code&gt; carries navigation, selection, clipboard, undo, and redo.&lt;/li&gt;
&lt;li&gt;Geometry queries locate the caret and selection for candidate windows and accessibility.&lt;/li&gt;
&lt;/ul&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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJQbGF0Zm9ybSBrZXlib2FyZCBvciBJTUUiXSAtLT4gQlsiQ29kZW5hbWUgT25lIHBvcnQgYmluZGluZyJdCiAgICBCIC0tPiBDWyJUZXh0SW5wdXRDbGllbnQgb3BlcmF0aW9ucyJdCiAgICBDIC0tPiBEWyJFZGl0b3JEb2N1bWVudCBhbmQgRWRpdG9yVmlldyJdCiAgICBEIC0tPiBFWyJDb2RlbmFtZSBPbmUgR3JhcGhpY3MsIHRleHQgc3RhdGUsIGFuZCBjYXJldCBnZW9tZXRyeSJd%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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJQbGF0Zm9ybSBrZXlib2FyZCBvciBJTUUiXSAtLT4gQlsiQ29kZW5hbWUgT25lIHBvcnQgYmluZGluZyJdCiAgICBCIC0tPiBDWyJUZXh0SW5wdXRDbGllbnQgb3BlcmF0aW9ucyJdCiAgICBDIC0tPiBEWyJFZGl0b3JEb2N1bWVudCBhbmQgRWRpdG9yVmlldyJdCiAgICBEIC0tPiBFWyJDb2RlbmFtZSBPbmUgR3JhcGhpY3MsIHRleHQgc3RhdGUsIGFuZCBjYXJldCBnZW9tZXRyeSJd%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="276" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All offsets use UTF-16 indices. That matches Java &lt;code&gt;String&lt;/code&gt;, Android &lt;code&gt;Editable&lt;/code&gt;, and Apple string APIs. The document normalizes line endings before it updates selection, undo history, formatting runs, or the state returned to the platform.&lt;/p&gt;

&lt;p&gt;The port still owns the keyboard session. Codename One owns the document and what appears on screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;EditField&lt;/code&gt; is the opt-in plain field
&lt;/h2&gt;

&lt;p&gt;Existing &lt;code&gt;TextField&lt;/code&gt; and &lt;code&gt;TextArea&lt;/code&gt; code does not change. Use &lt;code&gt;EditField&lt;/code&gt; when painting and editing must stay inside the Codename One component:&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;EditField&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;EditField&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;"Title"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TextArea&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ANY&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;setSingleLineTextArea&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;title&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setColumns&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;EditField&lt;/span&gt; &lt;span class="n"&gt;notes&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;EditField&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setSingleLineTextArea&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;notes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRows&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setColumns&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&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="s"&gt;"Issue"&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;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="n"&gt;title&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="n"&gt;notes&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;show&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;EditField&lt;/code&gt; extends the shared &lt;code&gt;EditorView&lt;/code&gt;. Caret movement, selection, scrolling, undo, clipboard commands, and input composition use the same mechanics as the rich-text and code editors.&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqrfhan7hi25x2333g0rm.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqrfhan7hi25x2333g0rm.png" alt="Rich text and code editors painted by the lightweight editing engine" width="320" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Text input is now a port-level capability that does not require a visible native field.&lt;/p&gt;

&lt;h2&gt;
  
  
  The clipboard carries several representations at once
&lt;/h2&gt;

&lt;p&gt;The old clipboard API usually moved a &lt;code&gt;String&lt;/code&gt;. That remains valid, but it cannot preserve formatting or describe an image or file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ClipboardContent&lt;/code&gt; represents one clipboard item through several MIME types. Include plain text as the fallback, then add the richer forms you can produce:&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;ClipboardContent&lt;/span&gt; &lt;span class="n"&gt;content&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;ClipboardContent&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ClipboardContent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MIME_TEXT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Codename One"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ClipboardContent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MIME_HTML&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;b&amp;gt;Codename One&amp;lt;/b&amp;gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ClipboardContent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MIME_MARKDOWN&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"**Codename One**"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&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;copyToClipboard&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&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 container supports RTF, AsciiDoc, PNG, JPEG, GIF, and local file references. Each port maps the representations its system clipboard exposes. Code that still calls &lt;code&gt;copyToClipboard("text")&lt;/code&gt; or expects plain text continues to work.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RichTextArea&lt;/code&gt; publishes five text representations when you copy a formatted selection: plain text, HTML, RTF, Markdown, and AsciiDoc. On paste, it selects the richest format it understands and imports the formatting into its document model. If the clipboard contains PNG, JPEG, or GIF bytes, the editor inserts the image inline as a self-contained data URI.&lt;/p&gt;

&lt;p&gt;This is format negotiation, not a private editor clipboard. Other applications can paste the native formats a port publishes, and application code can inspect the available types through &lt;code&gt;Display.getInstance().getClipboardContent()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bidirectional text uses one layout for paint and hit testing
&lt;/h2&gt;

&lt;p&gt;Mixed Hebrew, Arabic, numbers, and Latin text can produce a logical order different from the order on screen. It is not enough to shape the glyphs correctly. A click must land on the matching logical offset. Left and right movement must follow visual runs. A selection can span rectangles in both directions.&lt;/p&gt;

&lt;p&gt;The editor engine uses the same bidirectional runs for painting, hit testing, caret movement, and selection geometry. The component's RTL flag sets the paragraph base direction.&lt;/p&gt;

&lt;p&gt;That shared source of geometry also matters to native candidate windows. An East Asian IME needs to position its candidate list beside the composing range even though the visible field is not a native widget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rich and code editing stop being browser components
&lt;/h2&gt;

&lt;p&gt;We introduced &lt;code&gt;RichTextArea&lt;/code&gt; and &lt;code&gt;CodeEditor&lt;/code&gt; in June with a bundled web-view backend. That got the API into applications quickly, but it kept the hardest state inside HTML and JavaScript.&lt;/p&gt;

&lt;p&gt;The new implementation replaces the default web view with Java document and view objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EditorDocument
  normalized text and line offsets

EditorView
  caret, selection, composition, scrolling, clipboard, undo

CodeView
  incremental tokens, diagnostics, completion, gutter

RichView
  inline styles, blocks, links, image runs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pure engine is now the default on every port. A port can still provide a native editor peer when it has a better specialized implementation. Application commands and asynchronous queries remain the same either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The JavaScript port benefits twice
&lt;/h2&gt;

&lt;p&gt;The Java application runs in a Web Worker, while browser input events arrive on the main thread. The host creates an input surface for the keyboard and forwards semantic composition events across the worker boundary. The worker keeps the Java document, selection, and undo state.&lt;/p&gt;

&lt;p&gt;That matches the architecture described in &lt;a href="https://www.codenameone.com/blog/javascript-free-open-source/" rel="noopener noreferrer"&gt;Friday's release post&lt;/a&gt;: Java state stays in the worker, browser behavior stays in the port layer, and the bridge carries a defined message instead of sharing a DOM object.&lt;/p&gt;

&lt;p&gt;The Codename One Playground now uses the same &lt;code&gt;CodeEditor&lt;/code&gt; available to applications. It does not install another editor engine. Java and CSS diagnostics enter through &lt;code&gt;setDiagnostics(...)&lt;/code&gt;, while text input uses the same worker-to-host contract as other lightweight fields.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to keep the native overlay
&lt;/h2&gt;

&lt;p&gt;The native route is mature and appropriate for ordinary forms. It receives platform text behavior directly and costs less framework code.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;EditField&lt;/code&gt; or the pure editors when you need one of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The glyphs must not jump when editing begins.&lt;/li&gt;
&lt;li&gt;Text contains several styles or inline objects.&lt;/li&gt;
&lt;li&gt;Selection and painting must use application-specific rules.&lt;/li&gt;
&lt;li&gt;The same document model must run on every target.&lt;/li&gt;
&lt;li&gt;A mask or diagnostic must be drawn inside the text surface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are tradeoffs. Each port must implement composition, state updates, command mapping, and caret geometry correctly. A missed IME edge case belongs to our binding rather than to the native field. This release adds the architecture and broad tests, not a claim that every keyboard on every device has already been exercised.&lt;/p&gt;

&lt;p&gt;Use the native overlay for ordinary fields. Use the pure editing path when the Codename One component must own the text layout and painting.&lt;/p&gt;

&lt;p&gt;Tomorrow's post covers &lt;a href="https://www.codenameone.com/blog/rich-text-without-webview/" rel="noopener noreferrer"&gt;a lightweight rich text component without a web view&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Bluetooth Support Across Every Codename One Target</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Sun, 02 Aug 2026 14:25:14 +0000</pubDate>
      <link>https://dev.to/codenameone/bluetooth-support-across-every-codename-one-target-4om5</link>
      <guid>https://dev.to/codenameone/bluetooth-support-across-every-codename-one-target-4om5</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%2Fvq0cyqatej4n066mts1r.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%2Fvq0cyqatej4n066mts1r.jpg" alt="Bluetooth Support Across Every Codename One Target" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5399" rel="noopener noreferrer"&gt;PR #5399&lt;/a&gt; moves Bluetooth into the Codename One core and adds implementations for every target, including JavaScript. The API covers BLE central and peripheral roles, GATT, L2CAP, classic RFCOMM, simulator fixtures, and native desktop radios.&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;The old Cordova-derived cn1lib handled useful BLE cases on Android and iOS. The new implementation was written for Codename One and exposes the wider protocol surface through capability queries.&lt;/p&gt;

&lt;p&gt;Each target supports the roles available from its operating system. Browsers do not expose classic Bluetooth or peripheral mode. iOS does not expose arbitrary RFCOMM. The API reports these limits instead of pretending every operation works everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the role
&lt;/h2&gt;

&lt;p&gt;Bluetooth is a family of protocols and roles:&lt;/p&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;What it does&lt;/th&gt;
&lt;th&gt;Main targets&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BLE central&lt;/td&gt;
&lt;td&gt;Scan, connect, discover GATT, read, write, subscribe&lt;/td&gt;
&lt;td&gt;Android, iOS, desktop, JavaScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BLE peripheral&lt;/td&gt;
&lt;td&gt;Advertise and serve a local GATT database&lt;/td&gt;
&lt;td&gt;Android, iOS, simulator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L2CAP&lt;/td&gt;
&lt;td&gt;Bidirectional byte streams with more throughput than GATT chunks&lt;/td&gt;
&lt;td&gt;Android 10+, iOS 11+, simulator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Classic RFCOMM&lt;/td&gt;
&lt;td&gt;Serial streams for printers, scanners, and industrial devices&lt;/td&gt;
&lt;td&gt;Android, desktop, simulator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native simulator backend&lt;/td&gt;
&lt;td&gt;Drive the computer's real Bluetooth radio&lt;/td&gt;
&lt;td&gt;macOS, Linux, Windows simulator&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The entry point never returns &lt;code&gt;null&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;Bluetooth&lt;/span&gt; &lt;span class="n"&gt;bt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Bluetooth&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="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;bt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isLeSupported&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;hideBluetoothFeature&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are matching queries for peripheral mode, L2CAP, and classic Bluetooth. A target without a role fails with the typed &lt;code&gt;BluetoothError.NOT_SUPPORTED&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scan, connect, then discover
&lt;/h2&gt;

&lt;p&gt;This scan looks for the standard Heart Rate service and a device name beginning with &lt;code&gt;Polar&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;Bluetooth&lt;/span&gt; &lt;span class="n"&gt;bt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Bluetooth&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="n"&gt;bt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requestPermissions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;BluetoothPermission&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SCAN&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;BluetoothPermission&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CONNECT&lt;/span&gt;
&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;onResult&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;granted&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;permissionErr&lt;/span&gt;&lt;span class="o"&gt;)&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;permissionErr&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;granted&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="nc"&gt;ScanSettings&lt;/span&gt; &lt;span class="n"&gt;settings&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;ScanSettings&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;ScanFilter&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setServiceUuid&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BluetoothUuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromShort&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x180D&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setNamePrefix&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Polar"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

    &lt;span class="nc"&gt;BleScan&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;scan&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;BleScan&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;scan&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLE&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;startScan&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sighting&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;scan&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="na"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;sighting&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPeripheral&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;connect&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onResult&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;peripheral&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connectErr&lt;/span&gt;&lt;span class="o"&gt;)&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;connectErr&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;peripheral&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;discoverServices&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="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filters inside one &lt;code&gt;ScanFilter&lt;/code&gt; are combined with AND. Several filters in &lt;code&gt;ScanSettings&lt;/code&gt; are combined with OR. Several scans can run at once, and the native scan stops when the final handle stops.&lt;/p&gt;

&lt;p&gt;GATT operations return independent &lt;code&gt;AsyncResource&lt;/code&gt; values. The implementation serializes them through one queue per peripheral because platform stacks allow one in-flight request per connection. A missing platform callback times out instead of wedging every later operation.&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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJBcHBsaWNhdGlvbiJdIC0tPiBCWyJCbHVldG9vdGggY2FwYWJpbGl0eSBBUEkiXQogICAgQiAtLT4gQ1siU3VwcG9ydGVkIHJvbGU6IEJMRSBjZW50cmFsLCBCTEUgcGVyaXBoZXJhbCwgTDJDQVAsIG9yIFJGQ09NTSJdCiAgICBDIC0tPiBEWyJDb25uZWN0aW9uIGFuZCBvcGVyYXRpb24gcXVldWUiXQogICAgRCAtLT4gRVsiVGFyZ2V0IGJhY2tlbmQ6IEFuZHJvaWQsIEFwcGxlLCBkZXNrdG9wLCBicm93c2VyLCBvciBzaW11bGF0b3IiXQogICAgRSAtLT4gRlsiUGxhdGZvcm0gQmx1ZXRvb3RoIHN0YWNrIG9yIFdlYiBCbHVldG9vdGgiXQ%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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJBcHBsaWNhdGlvbiJdIC0tPiBCWyJCbHVldG9vdGggY2FwYWJpbGl0eSBBUEkiXQogICAgQiAtLT4gQ1siU3VwcG9ydGVkIHJvbGU6IEJMRSBjZW50cmFsLCBCTEUgcGVyaXBoZXJhbCwgTDJDQVAsIG9yIFJGQ09NTSJdCiAgICBDIC0tPiBEWyJDb25uZWN0aW9uIGFuZCBvcGVyYXRpb24gcXVldWUiXQogICAgRCAtLT4gRVsiVGFyZ2V0IGJhY2tlbmQ6IEFuZHJvaWQsIEFwcGxlLCBkZXNrdG9wLCBicm93c2VyLCBvciBzaW11bGF0b3IiXQogICAgRSAtLT4gRlsiUGxhdGZvcm0gQmx1ZXRvb3RoIHN0YWNrIG9yIFdlYiBCbHVldG9vdGgiXQ%3D%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="276" height="734"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Bluetooth supports central GATT
&lt;/h2&gt;

&lt;p&gt;On the JavaScript port, &lt;code&gt;startScan(...)&lt;/code&gt; opens the browser's device chooser. The chooser uses your filters and returns the one device the user selects.&lt;/p&gt;

&lt;p&gt;That call must happen from a user gesture, normally a button action. It requires HTTPS in a Chrome-family browser. The browser does not provide RSSI, repeated advertisement sightings, classic Bluetooth, L2CAP, or the peripheral role.&lt;/p&gt;

&lt;p&gt;The same Java central-role code can still connect, discover services, read, write, and subscribe. The browser owns the permission prompt and device selection.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Display.getPlatformName().equals("js")&lt;/code&gt; tells you where the app runs. &lt;code&gt;isPeripheralModeSupported()&lt;/code&gt; tells you whether the operation you need can work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The simulator can fail on command
&lt;/h2&gt;

&lt;p&gt;Hardware tests fail in the least convenient places. A device disappears between scan and connect. A GATT read never calls back. The radio turns off after a screen opens. Reproducing those cases with a physical sensor is slow.&lt;/p&gt;

&lt;p&gt;The simulator now has a virtual Bluetooth stack:&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkjlxushsk0wra84w32v7.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkjlxushsk0wra84w32v7.png" alt="Bluetooth simulator with a virtual device tree and characteristic editor" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can stage peripherals, edit characteristic bytes, delay every callback, disconnect a device remotely, and arm the next scan, connect, read, write, discover, or subscribe operation to fail with a selected &lt;code&gt;BluetoothError&lt;/code&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpxfcwpvfx7uvny2zjofc.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpxfcwpvfx7uvny2zjofc.png" alt="Bluetooth simulator event log during scan and connection" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Portable tests can drive the built-in demo device without importing simulator classes:&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="no"&gt;CN&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bluetooth:item2"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// add SimulatedSensor&lt;/span&gt;
&lt;span class="no"&gt;CN&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bluetooth:item3"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// send a notification&lt;/span&gt;
&lt;span class="no"&gt;CN&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bluetooth:item8"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// fail the next GATT read&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The native simulator backend takes the other route. It loads a Rust &lt;code&gt;btleplug&lt;/code&gt; library and drives the host computer's real radio through CoreBluetooth, BlueZ, or WinRT. That gives you real central-role traffic without leaving the Codename One simulator.&lt;/p&gt;

&lt;p&gt;Record and replay connects the two. A fixture capture scans real devices, records advertisements and GATT data, scrambles device identities, and saves JSON. The virtual stack can replay the timing and RSSI changes later in CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The builder injects only the roles you use
&lt;/h2&gt;

&lt;p&gt;Referencing &lt;code&gt;com.codename1.bluetooth&lt;/code&gt; is the build signal. The builders inspect which packages and types the application uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A central-only Android app does not receive advertise permissions.&lt;/li&gt;
&lt;li&gt;A BLE-only app does not declare classic Bluetooth hardware.&lt;/li&gt;
&lt;li&gt;An app with no Bluetooth references gets no Bluetooth manifest, plist, framework, or native code changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters because modern Bluetooth permissions are not one checkbox. Android separates scan, connect, and advertise. iOS requires a reason string and has different background modes. Beacon applications must also opt out of Android's default &lt;code&gt;neverForLocation&lt;/code&gt; declaration or Android 12+ can filter advertisements used for location.&lt;/p&gt;

&lt;p&gt;The generated defaults get a build through the toolchain. Your application still needs a specific privacy explanation for Apple review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streams still block
&lt;/h2&gt;

&lt;p&gt;Callbacks, scan results, notifications, and adapter changes arrive on the Codename One event dispatch thread. RFCOMM and L2CAP streams are the exception. Their reads and writes block, so they belong on a background thread.&lt;/p&gt;

&lt;p&gt;Addresses have another limit. Android and desktop may expose a MAC address. iOS returns a CoreBluetooth identifier scoped to the application. Persist the value for reconnection on the same install, but do not treat it as a portable hardware identity.&lt;/p&gt;

&lt;p&gt;The core API replaces the old cn1lib for new applications. It supports a wider set of Bluetooth roles, lets tests reproduce failures without a physical device, and keeps unused native pieces out of the build.&lt;/p&gt;

&lt;p&gt;Tomorrow's post covers &lt;a href="https://www.codenameone.com/blog/text-input-without-native-overlay/" rel="noopener noreferrer"&gt;pure Codename One text editing without native overlays&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Calendar API: Local Calendars, Cloud Sync, and Conflict Handling</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Sat, 01 Aug 2026 14:23:54 +0000</pubDate>
      <link>https://dev.to/codenameone/calendar-api-local-calendars-cloud-sync-and-conflict-handling-1a1p</link>
      <guid>https://dev.to/codenameone/calendar-api-local-calendars-cloud-sync-and-conflict-handling-1a1p</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%2Fnlficujg8f3oj6c8om6j.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%2Fnlficujg8f3oj6c8om6j.jpg" alt="Calendar API: Local Calendars, Cloud Sync, and Conflict Handling" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5413" rel="noopener noreferrer"&gt;PR #5413&lt;/a&gt; adds &lt;code&gt;com.codename1.calendar&lt;/code&gt;, a cross-platform API for local calendars and cloud providers. It covers events, tasks, recurrence, incremental sync, offline changes, and conflict handling.&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;A request to “add this event to the calendar” often grows into editing, recurrence, provider synchronization, and conflict resolution. The new API handles those requirements through one data model instead of a platform-specific intent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Events, recurrence, and tasks
&lt;/h2&gt;

&lt;p&gt;An event can be timed or all-day. It can recur, invite attendees, carry alarms, link a conference, and belong to a provider with its own identifier and version.&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;CalendarEvent&lt;/span&gt; &lt;span class="n"&gt;event&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;CalendarEvent&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCalendarId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calendarId&lt;/span&gt;&lt;span class="o"&gt;)&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;"Architecture review"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStart&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CalendarDateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instant&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ZoneId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Europe/Paris"&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setEnd&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CalendarDateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instant&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ZoneId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Europe/Paris"&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRecurrence&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;CalendarRecurrenceRule&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setFrequency&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CalendarRecurrenceRule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Frequency&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WEEKLY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addDayOfWeek&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAttendee&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;CalendarAttendee&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Ari"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setEmail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ari@example.com"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAlarm&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;CalendarAlarm&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTimeBefore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofMinutes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;

&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;saveEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;CalendarMutationScope&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ALL&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;saved&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;p&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Created "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API uses &lt;code&gt;java.time&lt;/code&gt;. Timed values carry a zone. All-day values use a date with no invented midnight. That distinction prevents an all-day event from moving to the previous day when it crosses a time-zone boundary.&lt;/p&gt;

&lt;p&gt;Tasks use the same source model without pretending they are events. Google Calendar and Google Tasks share one provider class. Microsoft calendars and Microsoft To Do share another.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask about capabilities, not platforms
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;LocalCalendarSource&lt;/code&gt; maps to Android's Calendar Provider and Apple EventKit on iOS and Mac Catalyst. The simulator gives tests an isolated in-memory calendar. It never writes to the developer's real calendar.&lt;/p&gt;

&lt;p&gt;Online providers work anywhere the HTTP and OAuth layers work, including JavaScript 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%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IFRECiAgICBBWyJBcHBsaWNhdGlvbiBjYWxlbmRhciBtb2RlbCJdIC0tPiBCWyJDYWxlbmRhclNvdXJjZSBjYXBhYmlsaXR5IGNvbnRyYWN0Il0KICAgIEIgLS0-IENbIlNlbGVjdGVkIHNvdXJjZTogQW5kcm9pZCwgQXBwbGUsIHNpbXVsYXRvciwgR29vZ2xlLCBNaWNyb3NvZnQsIENhbERBViwgb3IgUkZDIDU1NDUiXQogICAgQyAtLT4gRFsiQ2FwYWJpbGl0eSBxdWVyaWVzIGV4cG9zZSBzdXBwb3J0ZWQgb3BlcmF0aW9ucyJdCiAgICBEIC0tPiBFWyJDaGFuZ2UgbGlzdGVuZXJzLCBwYWdpbmcsIHByb3ZpZGVyIHZlcnNpb25zLCBhbmQgZGVsdGEgdG9rZW5zIl0%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%2FZmxvd2NoYXJ0IFRECiAgICBBWyJBcHBsaWNhdGlvbiBjYWxlbmRhciBtb2RlbCJdIC0tPiBCWyJDYWxlbmRhclNvdXJjZSBjYXBhYmlsaXR5IGNvbnRyYWN0Il0KICAgIEIgLS0-IENbIlNlbGVjdGVkIHNvdXJjZTogQW5kcm9pZCwgQXBwbGUsIHNpbXVsYXRvciwgR29vZ2xlLCBNaWNyb3NvZnQsIENhbERBViwgb3IgUkZDIDU1NDUiXQogICAgQyAtLT4gRFsiQ2FwYWJpbGl0eSBxdWVyaWVzIGV4cG9zZSBzdXBwb3J0ZWQgb3BlcmF0aW9ucyJdCiAgICBEIC0tPiBFWyJDaGFuZ2UgbGlzdGVuZXJzLCBwYWdpbmcsIHByb3ZpZGVyIHZlcnNpb25zLCBhbmQgZGVsdGEgdG9rZW5zIl0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="276" height="654"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do not branch on &lt;code&gt;Display.getPlatformName()&lt;/code&gt;. Ask the source:&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;LocalCalendarSource&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalCalendarSource&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="nc"&gt;CalendarCapabilities&lt;/span&gt; &lt;span class="n"&gt;capabilities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCapabilities&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;capabilities&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;supports&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CalendarCapability&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;READ_EVENTS&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requestAuthorization&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CalendarAccess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;EVENTS_READ_ONLY&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;status&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;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;CalendarAuthorizationStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;FULL&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;queryEvents&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;CalendarQuery&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCalendarId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStartTime&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&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;page&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getItems&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&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;Windows, native macOS desktop, Linux, and JavaScript currently report no built-in local calendar. They can still use Google, Microsoft, CalDAV, and &lt;code&gt;.ics&lt;/code&gt;. An empty capability set is more useful than a method that compiles and fails after deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  A version token prevents silent overwrites
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;version&lt;/code&gt; on a returned event is an optimistic concurrency token. Save the returned object when editing it. If somebody changed the provider copy after your read, the save returns &lt;code&gt;CalendarError.CONFLICT&lt;/code&gt; instead of overwriting the newer change.&lt;/p&gt;

&lt;p&gt;Offline mutation storage is opt-in:&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;CalendarSyncEngine&lt;/span&gt; &lt;span class="n"&gt;sync&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;CalendarSyncEngine&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;google&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;StorageCalendarCache&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"google-account-1"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;queueEventSave&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;CalendarMutationScope&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ALL&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sync&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;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;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CalendarConflict&lt;/span&gt; &lt;span class="n"&gt;conflict&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;getConflicts&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Show local and remote versions, then choose&lt;/span&gt;
        &lt;span class="c1"&gt;// KEEP_LOCAL, KEEP_REMOTE, or MERGED.&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 cache stores calendar data and pending mutations. It does not store OAuth credentials. Your application decides when background work runs and how a person resolves a conflict.&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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJBcHBsaWNhdGlvbiBxdWV1ZXMgYW4gZWRpdCB3aXRoIHZlcnNpb24gMTciXSAtLT4gQlsiU3luYyBzZW5kcyB0aGUgcGVuZGluZyBlZGl0IHRvIHRoZSBwcm92aWRlciJdCiAgICBCIC0tPiBDWyJQcm92aWRlciByZXBvcnRzIGEgY29uZmxpY3Qgd2l0aCByZW1vdGUgdmVyc2lvbiAxOCJdCiAgICBDIC0tPiBEWyJBcHBsaWNhdGlvbiBjaG9vc2VzIGxvY2FsLCByZW1vdGUsIG9yIG1lcmdlZCBjb250ZW50Il0KICAgIEQgLS0-IEVbIlN5bmMgc2F2ZXMgdGhlIHJlc29sdmVkIHZlcnNpb24iXQogICAgRSAtLT4gRlsiTG9jYWwgY2FjaGUgcmVjb3JkcyB0aGUgbmV3IHZlcnNpb24gYW5kIGRlbHRhIHRva2VuIl0%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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJBcHBsaWNhdGlvbiBxdWV1ZXMgYW4gZWRpdCB3aXRoIHZlcnNpb24gMTciXSAtLT4gQlsiU3luYyBzZW5kcyB0aGUgcGVuZGluZyBlZGl0IHRvIHRoZSBwcm92aWRlciJdCiAgICBCIC0tPiBDWyJQcm92aWRlciByZXBvcnRzIGEgY29uZmxpY3Qgd2l0aCByZW1vdGUgdmVyc2lvbiAxOCJdCiAgICBDIC0tPiBEWyJBcHBsaWNhdGlvbiBjaG9vc2VzIGxvY2FsLCByZW1vdGUsIG9yIG1lcmdlZCBjb250ZW50Il0KICAgIEQgLS0-IEVbIlN5bmMgc2F2ZXMgdGhlIHJlc29sdmVkIHZlcnNpb24iXQogICAgRSAtLT4gRlsiTG9jYWwgY2FjaGUgcmVjb3JkcyB0aGUgbmV3IHZlcnNpb24gYW5kIGRlbHRhIHRva2VuIl0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="276" height="734"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Creating the event is one operation. The version and sync APIs preserve the user's changes when another device or provider edits the same record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provider sync is pull based
&lt;/h2&gt;

&lt;p&gt;Local stores can emit &lt;code&gt;CalendarChange&lt;/code&gt; callbacks. Online providers return sync or delta tokens. Store the token and pass it with the next query.&lt;/p&gt;

&lt;p&gt;The API deliberately leaves Google and Microsoft webhooks to the application backend. Provider webhooks require a public endpoint, secret handling, renewal, and delivery policy that a client API cannot supply for every application.&lt;/p&gt;

&lt;p&gt;Google and Microsoft OAuth credentials also belong to your application. The provider classes never embed a shared Codename One client secret and never persist credentials on your behalf.&lt;/p&gt;

&lt;p&gt;CalDAV supports Basic, Bearer, and Digest authentication. Basic should only travel over HTTPS. Server implementations differ. Each asynchronous result can still report a collection-specific restriction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Import and export use the same model
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ICalendarCodec&lt;/code&gt; reads and writes RFC 5545 events, tasks, alarms, recurrence, attendees, time zones, URI attachments, and unknown &lt;code&gt;X-&lt;/code&gt; properties:&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;ics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ICalendarCodec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;CalendarEvent&lt;/span&gt; &lt;span class="n"&gt;imported&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ICalendarCodec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ics&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The codec supports email attachments, backups, provider interop, and ports with no local calendar service. Imported objects enter the same save, query, and conflict flow as objects created through the Java API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unused applications carry none of it
&lt;/h2&gt;

&lt;p&gt;The builders detect calls to the local calendar entry points. Android then adds calendar permissions. Apple builds add EventKit and the relevant privacy strings.&lt;/p&gt;

&lt;p&gt;An application that never references local calendar integration gets no permission or entitlement change. Online providers do not require device-calendar permissions.&lt;/p&gt;

&lt;p&gt;The portability boundary remains explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local change notifications depend on the operating system.&lt;/li&gt;
&lt;li&gt;Online notification is token-based polling, not an installed webhook.&lt;/li&gt;
&lt;li&gt;OAuth setup and credential storage remain application responsibilities.&lt;/li&gt;
&lt;li&gt;Conflict resolution requires a product decision. The framework cannot decide which edit matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use the platform intent when your requirement ends after creating one event. Use &lt;code&gt;CalendarSource&lt;/code&gt; when your application must read, update, synchronize, or resolve conflicts.&lt;/p&gt;

&lt;p&gt;Tomorrow's post covers &lt;a href="https://www.codenameone.com/blog/bluetooth-beyond-ble/" rel="noopener noreferrer"&gt;Bluetooth support across every Codename One target&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>The Codename One JavaScript Port Is Now Free and Open Source</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Fri, 31 Jul 2026 15:13:59 +0000</pubDate>
      <link>https://dev.to/codenameone/the-codename-one-javascript-port-is-now-free-and-open-source-2f84</link>
      <guid>https://dev.to/codenameone/the-codename-one-javascript-port-is-now-free-and-open-source-2f84</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%2Fk60s7jmdrlt4t53tu74r.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%2Fk60s7jmdrlt4t53tu74r.jpg" alt="The Codename One JavaScript Port Is Now Free and Open Source" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Codename One JavaScript port is now open source and available on every plan, including Free. You can also build the JavaScript target locally without a Codename One account.&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;a href="https://github.com/codenameone/CodenameOne/pull/5423" rel="noopener noreferrer"&gt;PR #5423&lt;/a&gt; makes our ParparVM-based port the default and moves its source under the same GPLv2 with Classpath Exception license as the rest of Codename One. TeaVM remains available as a fallback.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
JavaScript: The ParparVM JavaScript port is now open source, free on every plan, and buildable locally without an account.&lt;/li&gt;
&lt;li&gt;
Android API level 36: API 36 becomes the default next week, and you can test or temporarily override it with build hints.&lt;/li&gt;
&lt;li&gt;
ParparVM garbage collection: A real application exposed a benchmark gap, redundant collection cycles, and a broken first fix.&lt;/li&gt;
&lt;li&gt;
Calendar: The new API covers device and cloud calendars, recurrence, tasks, incremental sync, offline mutations, and conflict handling.&lt;/li&gt;
&lt;li&gt;
Bluetooth: Bluetooth moved into the core with BLE, GATT, L2CAP, RFCOMM, Web Bluetooth, desktop radios, and a scriptable simulator.&lt;/li&gt;
&lt;li&gt;
Text editing: &lt;code&gt;EditField&lt;/code&gt;, &lt;code&gt;RichTextArea&lt;/code&gt;, and &lt;code&gt;CodeEditor&lt;/code&gt; can edit and paint text without placing a native field over the component. The new clipboard model carries rich text, images, and file references alongside a plain-text fallback.&lt;/li&gt;
&lt;li&gt;
Rich text: &lt;code&gt;RichTextComponent&lt;/code&gt; renders HTML, Markdown, AsciiDoc, RTF, and styled Java runs without a web view.&lt;/li&gt;
&lt;li&gt;
Compact strings: Strings now use a &lt;code&gt;byte[]&lt;/code&gt; when every character fits, cutting their character storage in half without adding a second backing-array pointer.&lt;/li&gt;
&lt;li&gt;
Account, website, and videos: The authorization migration is complete, the redesigned site is live, and the weekly posts now generate explainer videos.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why we built the ParparVM JavaScript port
&lt;/h2&gt;

&lt;p&gt;Our original web port used &lt;a href="https://teavm.org/" rel="noopener noreferrer"&gt;TeaVM&lt;/a&gt;, which is a fantastic project. TeaVM took Java bytecode, supported the threading model Codename One needed, and produced a browser application at a time when that combination looked close to impossible.&lt;/p&gt;

&lt;p&gt;ParparVM already powered other Codename One targets when we built a second JavaScript route on top of it. If we had understood how much work this would require, we might have spent that effort improving the TeaVM integration instead. The finished port gives us direct control over the VM, runtime, browser integration, and test infrastructure.&lt;/p&gt;

&lt;p&gt;VM-level APIs can now reach the web target through the same translator and Java runtime work used by our native ports. We no longer need a separate implementation in a third-party runtime before the browser can use a new VM API.&lt;/p&gt;

&lt;p&gt;Shared framework and runtime changes also trigger the ParparVM browser suite. Pull requests render and compare the Codename One test application in Chromium. A nightly job starts generated applications in Chromium, Firefox, and WebKit.&lt;/p&gt;

&lt;p&gt;The port also keeps Java and browser code separate:&lt;/p&gt;

&lt;p&gt;The old route ran translated Java beside its JavaScript integration on the browser's main thread. The ParparVM route puts the translated Java application in a background Web Worker instead. The Java code has no &lt;code&gt;document&lt;/code&gt;, no DOM, and no dependency on global browser objects.&lt;/p&gt;

&lt;p&gt;A thin message bridge connects the worker to the standard front-end &lt;code&gt;JavaScriptPort&lt;/code&gt;, which runs on the browser's main thread. The port handles the canvas, DOM, clipboard, media, Web Bluetooth, and other browser APIs. Application-specific browser code stays under &lt;code&gt;native/JavaScript&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Browser APIs still impose browser rules. Web Bluetooth requires HTTPS and a user gesture, for example. Those requirements stay in the port layer instead of affecting ordinary Java code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the web target without our cloud
&lt;/h2&gt;

&lt;p&gt;Generated Maven projects now include a local JavaScript 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 &lt;span class="nt"&gt;-pl&lt;/span&gt; javascript package &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-Dcodename1&lt;/span&gt;.platform&lt;span class="o"&gt;=&lt;/span&gt;javascript &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-Dcodename1&lt;/span&gt;.buildTarget&lt;span class="o"&gt;=&lt;/span&gt;local-javascript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The build uses the bundled ParparVM compiler and JavaScriptPort resources. It produces a static browser application on your machine. The same command is wired into generated IntelliJ, Eclipse, NetBeans, and VS Code projects.&lt;/p&gt;

&lt;p&gt;Cloud builds also use ParparVM by default. If you encounter a regression, compare it with the original TeaVM route by adding one 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="py"&gt;javascript.port&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;teavm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full selector is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Build hint&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;javascript.port=parparvm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Current JavaScriptPort builder and the default.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;javascript.port=teavm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Original TeaVM builder for compatibility and diagnosis.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;TeaVM is not being removed. It remains useful for compatibility testing while ParparVM gains more production use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The proxy is part of deployment now
&lt;/h2&gt;

&lt;p&gt;A browser application can call its own origin directly. Calls to another origin depend on that server's CORS policy. Codename One has traditionally handled the second case with a servlet proxy bundled into the TeaVM WAR.&lt;/p&gt;

&lt;p&gt;That proxy came from an older Java EE stack. It calls a servlet API removed from current Jakarta containers, so changing &lt;code&gt;javax.servlet&lt;/code&gt; imports to &lt;code&gt;jakarta.servlet&lt;/code&gt; would not fix it.&lt;/p&gt;

&lt;p&gt;The ParparVM builder now creates a proxy wrapper for the deployment target you choose:&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="py"&gt;javascript.proxy.target&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;cloudflare-workers&lt;/span&gt;
&lt;span class="py"&gt;javascript.proxy.allowedTargets&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://api.example.com,*.services.example.org&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Available targets are &lt;code&gt;jakarta-servlet&lt;/code&gt;, &lt;code&gt;javax-servlet&lt;/code&gt;, &lt;code&gt;node&lt;/code&gt;, &lt;code&gt;php&lt;/code&gt;, &lt;code&gt;aws-lambda&lt;/code&gt;, &lt;code&gt;google-cloud-functions&lt;/code&gt;, &lt;code&gt;cloudflare-workers&lt;/code&gt;, and &lt;code&gt;none&lt;/code&gt;. Jakarta Servlet is the default.&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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJDb2RlbmFtZSBPbmUgd2ViIGFwcCByZXF1ZXN0cyAvY24xLWNvcnMtcHJveHkiXSAtLT4gQlsiU2FtZS1vcmlnaW4gcHJveHkgY2hlY2tzIHRoZSBVUkwgc2NoZW1lIGFuZCBhbGxvd2xpc3QiXQogICAgQiAtLT4gQ1siUHJveHkgZm9yd2FyZHMgdGhlIHJlcXVlc3QgdG8gdGhlIGFwcHJvdmVkIHJlbW90ZSBBUEkiXQogICAgQyAtLT4gRFsiUmVtb3RlIEFQSSByZXR1cm5zIHN0YXR1cywgaGVhZGVycywgYW5kIGJvZHkiXQogICAgRCAtLT4gRVsiUHJveHkgcmV0dXJucyBhIGJyb3dzZXItcmVhZGFibGUgcmVzcG9uc2UgdG8gdGhlIGFwcCJd%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%2FZmxvd2NoYXJ0IFRCCiAgICBBWyJDb2RlbmFtZSBPbmUgd2ViIGFwcCByZXF1ZXN0cyAvY24xLWNvcnMtcHJveHkiXSAtLT4gQlsiU2FtZS1vcmlnaW4gcHJveHkgY2hlY2tzIHRoZSBVUkwgc2NoZW1lIGFuZCBhbGxvd2xpc3QiXQogICAgQiAtLT4gQ1siUHJveHkgZm9yd2FyZHMgdGhlIHJlcXVlc3QgdG8gdGhlIGFwcHJvdmVkIHJlbW90ZSBBUEkiXQogICAgQyAtLT4gRFsiUmVtb3RlIEFQSSByZXR1cm5zIHN0YXR1cywgaGVhZGVycywgYW5kIGJvZHkiXQogICAgRCAtLT4gRVsiUHJveHkgcmV0dXJucyBhIGJyb3dzZXItcmVhZGFibGUgcmVzcG9uc2UgdG8gdGhlIGFwcCJd%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="276" height="654"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;javascript.proxy.allowedTargets&lt;/code&gt; before public deployment. An omitted allowlist produces a build warning because an open forwarding proxy is not a safe default for production. You can also point at an existing proxy with &lt;code&gt;javascript.proxy.url&lt;/code&gt; or disable generation with &lt;code&gt;javascript.proxy.target=none&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we are making JavaScript available to everyone
&lt;/h2&gt;

&lt;p&gt;JavaScript builds were previously an Enterprise feature and a significant reason to purchase that plan. We expect this change to reduce some of that revenue. For a small company, that matters, and this was a difficult decision for me to make.&lt;/p&gt;

&lt;p&gt;Paid plans continue to fund the cloud build machines, push service, Crash Protection, support, version retention, and the people doing this work. If your team relies on the paid services, keep supporting the work through the plan that fits your needs.&lt;/p&gt;

&lt;p&gt;We do not know whether wider adoption will replace the revenue we lose. We do know that making the port easier to use and test is the right technical direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android API 36 becomes the default next week
&lt;/h2&gt;

&lt;p&gt;Some developers are already receiving Google Play notices about Android 16 and API level 36. &lt;a href="https://github.com/codenameone/CodenameOne/issues/4466" rel="noopener noreferrer"&gt;Issue #4466&lt;/a&gt; gave us time to test edge-to-edge behavior, resizing, permissions, and native integrations before changing the default.&lt;/p&gt;

&lt;p&gt;Try the new target now:&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="py"&gt;android.targetSDKVersion&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;36&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please send a build and exercise the parts of your application that touch system UI, background behavior, Bluetooth, notifications, and native libraries. Once 36 becomes the default, you can temporarily pin the previous toolchain while a regression is investigated:&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="py"&gt;android.buildToolsVersion&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;35&lt;/span&gt;
&lt;span class="py"&gt;android.targetSDKVersion&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;35&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Issues like #4466 are how a small team keeps pace with Apple, Google, browser vendors, desktop platforms, and hardware changes. If you see a platform announcement that could affect Codename One, open an issue early. A short warning months ahead is far cheaper than an emergency migration after a store deadline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issue #5425: Dave was right about our collector
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://github.com/codenameone/CodenameOne/issues/5425" rel="noopener noreferrer"&gt;issue #5425&lt;/a&gt;, Dave reported an iOS workload that took roughly five minutes while the same code finished in seconds on Android and on the older ParparVM collector. I focused on the allocation-heavy application code and dismissed the regression. Dave reduced the temporary allocation, kept measuring, and showed that my explanation did not fit the result.&lt;/p&gt;

&lt;p&gt;He was right. Our recent BiBOP collector work made short-lived small objects much cheaper, but the benchmark set did not cover a large retained population followed by larger allocations. The fixed trigger could run collection far too often even when the retained set gave it little to reclaim.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5436" rel="noopener noreferrer"&gt;PR #5436&lt;/a&gt; added an issue-shaped benchmark and adaptive pacing. The first fix also introduced a fresh-object tracing bug that could free a live child and leave its parent pointing at reused memory. Dave's repeated build against &lt;code&gt;master&lt;/code&gt; exposed corrupted dictionary words and an impossible null pointer. &lt;a href="https://github.com/codenameone/CodenameOne/pull/5442" rel="noopener noreferrer"&gt;PR #5442&lt;/a&gt; fixed that correctness regression and added a focused audit.&lt;/p&gt;

&lt;p&gt;Versioned builds against the current development head made that feedback loop possible:&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="py"&gt;build.cn1Version&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;master&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dave could test each collector revision in his real application while we changed it.&lt;/p&gt;

&lt;p&gt;Thanks, Dave. I needed the bump on the head.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calendar API: local calendars, cloud sync, and conflicts
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; &lt;code&gt;com.codename1.calendar&lt;/code&gt; provides one API for device calendars, Google, Microsoft, CalDAV, and &lt;code&gt;.ics&lt;/code&gt; files. It includes recurrence, tasks, provider versions, incremental sync, offline mutation queues, and conflict handling.&lt;/p&gt;
&lt;/blockquote&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnlficujg8f3oj6c8om6j.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%2Fnlficujg8f3oj6c8om6j.jpg" alt="Calendar API with local and cloud synchronization" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A request to add an event often grows into recurrence, edits from another device, offline changes, and a stale write that could overwrite a newer provider copy. The new API keeps those cases in one model and exposes the operations supported by each source.&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;LocalCalendarSource&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalCalendarSource&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="nc"&gt;CalendarCapabilities&lt;/span&gt; &lt;span class="n"&gt;capabilities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCapabilities&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;capabilities&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;supports&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CalendarCapability&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;READ_EVENTS&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;queryEvents&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&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;page&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getItems&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&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 simulator uses an isolated in-memory calendar, so tests never touch the developer's real schedule. &lt;a href="https://github.com/codenameone/CodenameOne/pull/5413" rel="noopener noreferrer"&gt;PR #5413&lt;/a&gt; contains the implementation. &lt;a href="https://www.codenameone.com/blog/calendar-is-not-add-event/" rel="noopener noreferrer"&gt;Read the calendar API article for recurrence, OAuth boundaries, conflict resolution, and provider synchronization.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bluetooth is now part of the core
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; The new core Bluetooth API supports every Codename One target, including JavaScript. It covers BLE central and peripheral roles, GATT, L2CAP streams, classic RFCOMM, browser Web Bluetooth, real desktop radios, and a scriptable simulator.&lt;/p&gt;
&lt;/blockquote&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkjlxushsk0wra84w32v7.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkjlxushsk0wra84w32v7.png" alt="Bluetooth simulator with a virtual device tree and characteristic editor" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bluetooth is larger than BLE scanning. A medical sensor might need notifications over GATT. A scanner or printer might use classic RFCOMM. Tests need to reproduce a failed connection or a callback that never arrives.&lt;/p&gt;

&lt;p&gt;The API reports capabilities instead of pretending every role works everywhere. Browsers provide central GATT through a user-controlled chooser. iOS does not expose arbitrary RFCOMM. Applications that do not reference Bluetooth receive none of its permissions or native code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5399" rel="noopener noreferrer"&gt;PR #5399&lt;/a&gt; is a ground-up replacement for the Cordova-derived cn1lib. &lt;a href="https://www.codenameone.com/blog/bluetooth-beyond-ble/" rel="noopener noreferrer"&gt;Read the Bluetooth article for the role model, platform matrix, GATT queue, simulator, and builder behavior.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pure Codename One text editing
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; &lt;code&gt;EditField&lt;/code&gt;, &lt;code&gt;RichTextArea&lt;/code&gt;, and &lt;code&gt;CodeEditor&lt;/code&gt; can now edit and paint text entirely inside the Codename One component layer. The operating system supplies keyboard and input-method operations without placing a native field over the component.&lt;/p&gt;
&lt;/blockquote&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqrfhan7hi25x2333g0rm.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqrfhan7hi25x2333g0rm.png" alt="Rich text and code editors painted by the lightweight editing engine" width="320" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Codename One traditionally places a native text field over a lightweight field while the user edits. That route remains the default for &lt;code&gt;TextField&lt;/code&gt; and &lt;code&gt;TextArea&lt;/code&gt;. It works well for ordinary forms, but it cannot paint syntax colors, inline objects, application-defined masks, or custom selections inside the Codename One surface.&lt;/p&gt;

&lt;p&gt;The new &lt;code&gt;TextInputClient&lt;/code&gt; contract sends semantic operations such as &lt;code&gt;commitText(...)&lt;/code&gt;, &lt;code&gt;setComposingText(...)&lt;/code&gt;, and &lt;code&gt;deleteSurroundingText(...)&lt;/code&gt;. The portable editor owns the document, caret, selection, bidirectional layout, undo state, and pixels.&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;EditField&lt;/span&gt; &lt;span class="n"&gt;notes&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;EditField&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setSingleLineTextArea&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;notes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRows&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&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="n"&gt;notes&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This work also replaces the text-only clipboard assumption. A &lt;code&gt;ClipboardContent&lt;/code&gt; can carry several representations of one item: plain text, HTML, RTF, Markdown, AsciiDoc, PNG, JPEG, GIF, and file references. The port publishes the formats its system clipboard supports while retaining plain text for older code and applications that do not understand the richer payload.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RichTextArea&lt;/code&gt; uses that negotiation directly. Copying a formatted selection publishes plain text, HTML, RTF, Markdown, and AsciiDoc together. Pasting chooses the richest text representation it understands, while a clipboard image becomes an inline image instead of a filename or discarded payload.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5386" rel="noopener noreferrer"&gt;PR #5386&lt;/a&gt; adds the port bindings and portable editor engine. &lt;a href="https://www.codenameone.com/blog/text-input-without-native-overlay/" rel="noopener noreferrer"&gt;Read the text-input article for composition, UTF-16 offsets, bidirectional hit testing, rich clipboard negotiation, and the native-overlay tradeoff.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lightweight rich text without a web view
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; &lt;code&gt;RichTextComponent&lt;/code&gt; renders HTML, Markdown, AsciiDoc, RTF, and styled Java runs inside ordinary Codename One layouts. It shares its document model and painter with &lt;code&gt;RichTextArea&lt;/code&gt; but has no editing session or browser peer.&lt;/p&gt;
&lt;/blockquote&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6kjlnyqec0ao4kxhleu5.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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6kjlnyqec0ao4kxhleu5.png" alt="Rich text with headings, emphasis, and a list" width="320" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;SpanLabel&lt;/code&gt; gives wrapped text one style. A &lt;code&gt;BrowserComponent&lt;/code&gt; provides a complete browser. &lt;code&gt;RichTextComponent&lt;/code&gt; fills the space between them for formatted application content.&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;RichTextComponent&lt;/span&gt; &lt;span class="n"&gt;view&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;RichTextComponent&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setMarkdown&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"# Trip summary\n\n"&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"Departs **09:40**. See the [itinerary](app://itinerary)."&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="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component measures its content inside the Codename One layout. Link handling, image loading, authentication, and caching remain under application control. The HTML importer handles document markup but does not execute scripts or implement a CSS page layout.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5421" rel="noopener noreferrer"&gt;PR #5421&lt;/a&gt; contains the viewer and shared painter. &lt;a href="https://www.codenameone.com/blog/rich-text-without-webview/" rel="noopener noreferrer"&gt;Read the rich-text article for supported formats, link and image policies, clipboard negotiation, and when to keep BrowserComponent.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Compact strings in ParparVM
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Strings now store their characters in a &lt;code&gt;byte[]&lt;/code&gt; when every character fits. Text that needs wider code units continues to use &lt;code&gt;char[]&lt;/code&gt;. This halves the character storage for common strings without adding a second array pointer to every &lt;code&gt;String&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&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%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5uns3l7tr5q7h0q7d30u.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%2F5uns3l7tr5q7h0q7d30u.jpg" alt="Compact strings cut character storage in half" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;URLs, JSON keys, class names, numbers, log messages, and much Western European text need one byte per character. ParparVM previously stored all of them in a two-byte &lt;code&gt;char[]&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;String&lt;/span&gt; &lt;span class="n"&gt;compact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Résumé 2026"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// byte[] backing&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;wide&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="c1"&gt;// char[] backing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation keeps one &lt;code&gt;Object value&lt;/code&gt; field whose concrete array type identifies the representation. The translator also preserves fused allocation, where the &lt;code&gt;String&lt;/code&gt; fields and backing storage occupy one BiBOP block. Application code does not change.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.codenameone.com/blog/compact-strings-parparvm/" rel="noopener noreferrer"&gt;Read the compact-strings article for the object layout, fused-allocation changes, native-operation audit, and Unicode behavior.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Account, website, and video updates
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Authorization migration
&lt;/h3&gt;

&lt;p&gt;We finished the account authorization migration started last week. We shut the old authorization server down too early, brought it back while we fixed the remaining paths, then shut it down again.&lt;/p&gt;

&lt;p&gt;You may need to sign in again the next time you send a build. If a fresh login does not work, contact us through the website chat. It is still the fastest route to the team.&lt;/p&gt;

&lt;h3&gt;
  
  
  New website design
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5402" rel="noopener noreferrer"&gt;PR #5402&lt;/a&gt; shipped a new website design and rewrote several core pages. Tell us what works, what gets in the way, and what we should improve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Videos generated from these posts
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.youtube.com/@CodenameOne" rel="noopener noreferrer"&gt;Codename One YouTube channel&lt;/a&gt; now has explainer videos generated from these posts. &lt;a href="https://github.com/codenameone/CodenameOne/pull/5441" rel="noopener noreferrer"&gt;PR #5441&lt;/a&gt; adds the video builder, written in Codename One, that turns a reviewed script into timed scenes, narration, code, subtitles, and a final video.&lt;/p&gt;

&lt;p&gt;Video editing is not my cup of tea. The generated voice does not bother me, and removing hours of narration and timeline work means we can cover more of what ships. The question is whether those videos help you. Should we invest in this format and use it to replace the aging courses, or would you rather see fewer videos with human narration? Please tell us.&lt;/p&gt;

</description>
      <category>java</category>
      <category>mobile</category>
      <category>android</category>
      <category>ios</category>
    </item>
    <item>
      <title>Port Support You Can Trace Back to a Green Test</title>
      <dc:creator>Shai Almog</dc:creator>
      <pubDate>Wed, 29 Jul 2026 15:06:17 +0000</pubDate>
      <link>https://dev.to/codenameone/port-support-you-can-trace-back-to-a-green-test-54in</link>
      <guid>https://dev.to/codenameone/port-support-you-can-trace-back-to-a-green-test-54in</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%2Fpcvv2te3cznc4cvwk5jh.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%2Fpcvv2te3cznc4cvwk5jh.jpg" alt="Port Support You Can Trace Back to a Green Test" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;“Supported on iOS, Android, desktop, and web” sounds useful until you need one method on one target. Does WebSocket work on watchOS? Which Linux architectures do we build? Was the JavaScript media test green this week, or did somebody update a table six months ago and forget 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;&lt;a href="https://github.com/codenameone/CodenameOne/pull/5389" rel="noopener noreferrer"&gt;PR #5389&lt;/a&gt; turns those questions into the &lt;a href="https://www.codenameone.com/port-status/" rel="noopener noreferrer"&gt;Codename One Port Status page&lt;/a&gt;. It maps 49 user-facing feature groups across 10 portability targets to current conformance results, environment data, skip reasons, and the date of the run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The table is an output, not an opinion
&lt;/h2&gt;

&lt;p&gt;The HelloCodenameOne suite already exercises APIs and screenshot goldens on Android, iOS, tvOS, watchOS, JavaScript, native Linux, native Windows, and Mac Catalyst. The missing part was a contract that translated thousands of test cases into a stable public vocabulary.&lt;/p&gt;

&lt;p&gt;The new conformance mapping connects registered tests and screenshots to rows such as networking, media, databases, maps, notifications, input, accessibility, and 3D. CI normalizes each port's result into the same report format. A publishing workflow writes the latest reports to a data-only branch. The website consumes those reports and renders the matrix.&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%2FZmxvd2NoYXJ0IExSCiAgICBBWyJQb3J0IENJIGpvYnMiXSAtLT4gQlsiSGVsbG9Db2RlbmFtZU9uZSB0ZXN0cyJdCiAgICBCIC0tPiBDWyJOb3JtYWxpemVkIGNvbmZvcm1hbmNlIHJlcG9ydCJdCiAgICBEWyJGZWF0dXJlLXRvLXRlc3QgY29udHJhY3QiXSAtLT4gRVsiNDkgcHVibGljIGZlYXR1cmUgcm93cyJdCiAgICBDIC0tPiBGWyJEYXRhLW9ubHkgc3RhdHVzIGJyYW5jaCJdCiAgICBFIC0tPiBGCiAgICBGIC0tPiBHWyIvcG9ydC1zdGF0dXMvIl0KICAgIEcgLS0-IEhbIjQ5MCB0YXJnZXQtZmVhdHVyZSBjZWxscyJdCiAgICBHIC0tPiBJWyJFbnZpcm9ubWVudCBhbmQgcnVuIGRhdGUiXQogICAgRyAtLT4gSlsiU2tpcCBhbmQgc2NvcGUgZXhwbGFuYXRpb25zIl0%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%2FZmxvd2NoYXJ0IExSCiAgICBBWyJQb3J0IENJIGpvYnMiXSAtLT4gQlsiSGVsbG9Db2RlbmFtZU9uZSB0ZXN0cyJdCiAgICBCIC0tPiBDWyJOb3JtYWxpemVkIGNvbmZvcm1hbmNlIHJlcG9ydCJdCiAgICBEWyJGZWF0dXJlLXRvLXRlc3QgY29udHJhY3QiXSAtLT4gRVsiNDkgcHVibGljIGZlYXR1cmUgcm93cyJdCiAgICBDIC0tPiBGWyJEYXRhLW9ubHkgc3RhdHVzIGJyYW5jaCJdCiAgICBFIC0tPiBGCiAgICBGIC0tPiBHWyIvcG9ydC1zdGF0dXMvIl0KICAgIEcgLS0-IEhbIjQ5MCB0YXJnZXQtZmVhdHVyZSBjZWxscyJdCiAgICBHIC0tPiBJWyJFbnZpcm9ubWVudCBhbmQgcnVuIGRhdGUiXQogICAgRyAtLT4gSlsiU2tpcCBhbmQgc2NvcGUgZXhwbGFuYXRpb25zIl0%3D%3Ftype%3Dpng%26bgColor%3Dffffff" alt="Diagram" width="1546" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The page currently renders 490 feature cells. Ten targets appear because architectures and renderer variants matter. iOS Metal and legacy OpenGL are separate evidence paths. Windows x64 and ARM64 are separate. Linux x64 and ARM64 are separate.&lt;/p&gt;

&lt;p&gt;JavaSE is deliberately excluded from the public portability matrix. It is the simulator and development runtime, not one of the deployed native targets the table is meant to prove.&lt;/p&gt;

&lt;h2&gt;
  
  
  A green cell has a chain of evidence
&lt;/h2&gt;

&lt;p&gt;Each status report records the commit, environment, registered tests, outcome, duration, and skipped cases. The website data also records the runtime used for browser and platform evidence. For example, the current browser environment file names the Chromium, Firefox, and WebKit engine versions rather than saying “modern browsers.”&lt;/p&gt;

&lt;p&gt;The contract itself is validated in CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 scripts/hellocodenameone/conformance/port_status.py validate
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; unittest &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  scripts/hellocodenameone/conformance/test_port_status.py
node scripts/website/validate_port_status.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validation fails if a registered conformance test or screenshot golden is orphaned from the public mapping, if the mapping points to a test that no longer exists, or if the published reports do not satisfy the schema. That makes the page part of the test system instead of a second table someone has to remember to edit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skipped does not always mean unsupported
&lt;/h2&gt;

&lt;p&gt;Some tests cannot run meaningfully on a target even when the API works. A device-only test may need hardware. A store API may require credentials. A screenshot can be irrelevant on watchOS because the phone-sized fixture is cropped before it tests the intended behavior.&lt;/p&gt;

&lt;p&gt;The status page keeps skips visible and attaches a reason. It does not silently convert every skip to “unsupported,” and it does not turn every skip into a green claim either. This distinction is important when a feature is supported but its current CI proof covers only part of the behavior.&lt;/p&gt;

&lt;p&gt;The deployment section applies the same standard to minimum versions. It separates the declared floor from the environment CI actually ran. An iOS build may compile with an iOS 14 deployment target while hosted CI runs the current Xcode 26 simulator. The page says both. A compiled floor is evidence, but it is not the same as running on an iOS 14 device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmarks use the same application
&lt;/h2&gt;

&lt;p&gt;The page also carries ten common workloads through each generated application: integer and long arithmetic, transcendental math, sequential and random arrays, allocation, map churn, string building, recursion, and quicksort.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3 warm-up runs
5 measured runs
report the minimum measured time
verify the workload checksum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are absolute per-target timings, not a claim that an ARM watch should beat a desktop CPU. Their value is trend detection and a common workload inside the actual generated port application.&lt;/p&gt;

&lt;p&gt;Binary size and memory are intentionally absent. The current artifacts mix compressed Android and web packages with unpacked Apple bundles and native executables. The ports also report different memory concepts. Publishing those numbers in one comparison row would look precise while measuring different things. They will return when a dedicated release-mode fixture packages and samples every target consistently.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a green test cannot prove
&lt;/h2&gt;

&lt;p&gt;A green status means the mapped tests passed in the named environment at the recorded commit. It does not prove that no application can hit a bug. It does not extend the test to OS versions, devices, drivers, or permissions that the run did not exercise.&lt;/p&gt;

&lt;p&gt;That boundary is why the page exposes details instead of collapsing everything to a marketing checkmark. You can inspect the target environment, last run, mapped coverage, and reason for an exception. If the proof is narrower than your requirement, the page should make that visible before you commit to a platform.&lt;/p&gt;

&lt;p&gt;This also changes how we review a new API. Adding the Java class is no longer enough. A feature needs a conformance test, a mapping to a public capability, and green results on the ports we claim. If a port intentionally does not implement it, that scope must be explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  One place to start a platform decision
&lt;/h2&gt;

&lt;p&gt;Use the &lt;a href="https://www.codenameone.com/port-status/" rel="noopener noreferrer"&gt;Port Status page&lt;/a&gt; when you need the current deployment floors, architecture coverage, API evidence, browser engines, or common-workload results. Then follow the linked test detail for the part your application depends on.&lt;/p&gt;

&lt;p&gt;This closes the week's series: &lt;a href="https://www.codenameone.com/blog/pixel-perfect-is-a-test/" rel="noopener noreferrer"&gt;measured native-theme fidelity&lt;/a&gt;, a &lt;a href="https://www.codenameone.com/blog/standalone-codename-one-settings/" rel="noopener noreferrer"&gt;standalone Settings tool&lt;/a&gt;, &lt;a href="https://www.codenameone.com/blog/widgets-live-activities-dynamic-island/" rel="noopener noreferrer"&gt;external surfaces&lt;/a&gt;, &lt;a href="https://www.codenameone.com/blog/accessibility-semantics/" rel="noopener noreferrer"&gt;portable accessibility semantics&lt;/a&gt;, and an &lt;a href="https://www.codenameone.com/blog/codename-one-mcp-server/" rel="noopener noreferrer"&gt;MCP server built on that semantic tree&lt;/a&gt;. The common thread is not the number of features. It is turning claims into artifacts you can inspect.&lt;/p&gt;

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