<?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: Tomer Aberbach</title>
    <description>The latest articles on DEV Community by Tomer Aberbach (@tomeraberbach).</description>
    <link>https://dev.to/tomeraberbach</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F293474%2Ff6fd314e-0e63-4e82-842f-aa8b86c26370.jpeg</url>
      <title>DEV Community: Tomer Aberbach</title>
      <link>https://dev.to/tomeraberbach</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tomeraberbach"/>
    <language>en</language>
    <item>
      <title>Making Java enums forwards compatible</title>
      <dc:creator>Tomer Aberbach</dc:creator>
      <pubDate>Tue, 11 Feb 2025 19:57:13 +0000</pubDate>
      <link>https://dev.to/stainless/making-java-enums-forwards-compatible-1cmm</link>
      <guid>https://dev.to/stainless/making-java-enums-forwards-compatible-1cmm</guid>
      <description>&lt;p&gt;SDKs, like most downloaded software, quickly end up with client-server &lt;a href="https://www.industrialempathy.com/posts/version-skew/" rel="noopener noreferrer"&gt;version skew&lt;/a&gt;. A great SDK gracefully handles skew by being forwards compatible with potential API changes.&lt;/p&gt;

&lt;p&gt;This is especially important for Java SDKs, which could to be used in Android apps that users take a long time to update (if they update at all). An app shouldn’t start crashing just because it’s not using the latest SDK version!&lt;/p&gt;

&lt;p&gt;It turns out Java enums are not trivially forwards compatible in this way.&lt;/p&gt;

&lt;h2&gt;
  
  
  An example API
&lt;/h2&gt;

&lt;p&gt;Suppose we’re designing a Java SDK for a Pet Store API. We might end up with an enum for representing order status:&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;enum&lt;/span&gt; &lt;span class="nc"&gt;PetOrderStatus&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;PLACED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;APPROVED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;DELIVERED&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;Users of our SDK would find this type more convenient than a &lt;code&gt;String&lt;/code&gt; because it indicates exactly which statuses are possible. Users can even use &lt;a href="https://docs.oracle.com/en/java/javase/17/language/switch-expressions-and-statements.html" rel="noopener noreferrer"&gt;“switch expressions”&lt;/a&gt; to ensure they handle all possible statuses:&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;displayText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;petOrderStatus&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;PLACED&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Your order was placed and is being reviewed."&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;APPROVED&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Your order was approved."&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;DELIVERED&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Your order was delivered!"&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;But how do we convert a string from the API response into an enum constant?&lt;/p&gt;

&lt;p&gt;The most common way to parse a string into an enum constant is using the built-in &lt;a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html#valueOf-java.lang.Class-java.lang.String-" rel="noopener noreferrer"&gt;&lt;code&gt;Enum.valueOf&lt;/code&gt;&lt;/a&gt; method, which is automatically generated for every enum:&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;PetOrderStatus&lt;/span&gt; &lt;span class="n"&gt;petOrderStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nc"&gt;PetOrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statusString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toUpperCase&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works, but there are problems lurking.&lt;/p&gt;

&lt;h2&gt;
  
  
  An API change
&lt;/h2&gt;

&lt;p&gt;Suppose one day our product manager pings us and says, “Customers want to know when their fluffball is on the way. Is that possible?”&lt;/p&gt;

&lt;p&gt;“Sounds reasonable,” we think to ourselves. So we implement the feature, add &lt;code&gt;"in_transit"&lt;/code&gt; as a possible order status in the API, and update our SDK and app like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; enum PetOrderStatus {
     PLACED,
     APPROVED,
&lt;span class="gi"&gt;+    IN_TRANSIT,
&lt;/span&gt;     DELIVERED,
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; String displayText = switch (petOrderStatus) {
     case PLACED -&amp;gt; "Your order was placed and is being reviewed.";
     case APPROVED -&amp;gt; "Your order was approved.";
&lt;span class="gi"&gt;+    case IN_TRANSIT -&amp;gt; "Your order is on the way!";
&lt;/span&gt;     case DELIVERED -&amp;gt; "Your order was delivered!";
 };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The feature works fine in our local environment so we decide to ship the changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crashes galore
&lt;/h2&gt;

&lt;p&gt;Almost immediately, we start getting crash reports for the Pet Store app. They all look like this:&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="p"&gt;Exception in thread "main" java.lang.IllegalArgumentException: No enum constant PetOrderStatus.IN_TRANSIT
&lt;/span&gt;    at java.base/java.lang.Enum.valueOf(Enum.java)
    at PetOrderStatus.valueOf(PetStoreApp.java)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But we &lt;em&gt;did&lt;/em&gt; update the SDK enum with an &lt;code&gt;IN_TRANSIT&lt;/code&gt; constant! What gives?&lt;/p&gt;

&lt;p&gt;It turns out that all of the crashes are coming from customers who haven’t updated their app yet. In that previous version, &lt;a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html#valueOf-java.lang.Class-java.lang.String-" rel="noopener noreferrer"&gt;&lt;code&gt;Enum.valueOf&lt;/code&gt;&lt;/a&gt; throws an &lt;code&gt;IllegalArgumentException&lt;/code&gt; when the input doesn’t match any known enum constant.&lt;/p&gt;

&lt;p&gt;The API started including &lt;code&gt;"in_transit"&lt;/code&gt; in responses, but almost all customers are still using the previous SDK version!&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;So what’s an SDK engineer to do? How could we have avoided this incident?&lt;/p&gt;

&lt;p&gt;One option is to make the type &lt;code&gt;Optional&amp;lt;PetOrderStatus&amp;gt;&lt;/code&gt; instead of just &lt;code&gt;PetOrderStatus&lt;/code&gt;, which would allow us to return &lt;code&gt;Optional.empty()&lt;/code&gt; when the value is not a known constant. This isn’t ideal for a few reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can’t access the raw API value in the &lt;code&gt;Optional.empty()&lt;/code&gt; case.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Optional.empty()&lt;/code&gt; looks like it means there’s no order status, but that’s not what we’re trying to convey.&lt;/li&gt;
&lt;li&gt;What if we want to represent the concept of “no order status” in the future? We would no longer be able to use &lt;code&gt;Optional.empty()&lt;/code&gt; for that!&lt;sup id="fnref1"&gt;1&lt;/sup&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A more robust solution would be to store the raw API value and provide access to both the raw value &lt;em&gt;and&lt;/em&gt; an enum representation of it.&lt;/p&gt;

&lt;p&gt;We might end up with a class that looks like this:&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;PetOrderStatus&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&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="nf"&gt;PetOrderStatus&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;value&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&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="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/** Used to "parse" the API value. */&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;PetOrderStatus&lt;/span&gt; &lt;span class="nf"&gt;of&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;value&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PetOrderStatus&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="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/** Returns the raw API value. */&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;_value&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/** Returns an enum containing known order statuses. */&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt; &lt;span class="nf"&gt;value&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;switch&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="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"placed"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PLACED&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"approved"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPROVED&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"delivered"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DELIVERED&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_UNKNOWN&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;};&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="no"&gt;PLACED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;APPROVED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;DELIVERED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="cm"&gt;/** The order status is not known to this SDK version. */&lt;/span&gt;
        &lt;span class="n"&gt;_UNKNOWN&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;And our SDK users could use the class like so:&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;displayText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;petOrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;PLACED&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Your order was placed and is being reviewed."&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;APPROVED&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Your order was approved."&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;DELIVERED&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Your order was delivered!"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Fallback for new order statuses not known to this SDK version.&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;_UNKNOWN&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Your order status is: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;petOrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;_value&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;This design has several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We still get all the benefits of enums, including exhaustiveness checks that now require us to handle the unknown status case.&lt;/li&gt;
&lt;li&gt;We can still use the raw API value in the unknown status case.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  An API change: part 2
&lt;/h2&gt;

&lt;p&gt;With this new SDK design, our app wouldn’t have started crashing after the API change. Instead, previous app versions would have displayed the following message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your order status is: in_transit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if we had made the following change to our SDK and app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; public final class PetOrderStatus {
     // ...
&lt;span class="err"&gt;
&lt;/span&gt;     public Value value() {
         return switch (value) {
             case "placed" -&amp;gt; Value.PLACED;
             case "approved" -&amp;gt; Value.APPROVED;
&lt;span class="gi"&gt;+            case "in_transit" -&amp;gt; Value.IN_TRANSIT; 
&lt;/span&gt;             case "delivered" -&amp;gt; Value.DELIVERED;
             default -&amp;gt; Value._UNKNOWN;
         };
     }
&lt;span class="err"&gt;
&lt;/span&gt;     public enum Value {
         PLACED,
         APPROVED,
&lt;span class="gi"&gt;+        IN_TRANSIT,
&lt;/span&gt;         DELIVERED,
         /** The order status is not known to this SDK version. */
         _UNKNOWN,
     }
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; String displayText = switch (petOrderStatus.value()) {
     case PLACED -&amp;gt; "Your order was placed and is being reviewed.";
     case APPROVED -&amp;gt; "Your order was approved.";
&lt;span class="gi"&gt;+    case IN_TRANSIT -&amp;gt; "Your order is on the way!";
&lt;/span&gt;     case DELIVERED -&amp;gt; "Your order was delivered!";
     // Fallback for new order statuses not known to this SDK version.
     case _UNKNOWN -&amp;gt; "Your order status is: " + petOrderStatus._value();
 };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then customers would get better display text the next time they update.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.stainlessapi.com/signup" rel="noopener noreferrer"&gt;Try Stainless today&lt;/a&gt;.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Yes, we could do &lt;code&gt;Optional&amp;lt;Optional&amp;lt;PetOrderStatus&amp;gt;&amp;gt;&lt;/code&gt;, but good luck getting that through design review. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>java</category>
      <category>api</category>
    </item>
  </channel>
</rss>
