<?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: Nextpatch</title>
    <description>The latest articles on DEV Community by Nextpatch (@nextpatch).</description>
    <link>https://dev.to/nextpatch</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%2F4143563%2F9b67a55f-79ba-4730-82ff-6eeb6bc6e442.png</url>
      <title>DEV Community: Nextpatch</title>
      <link>https://dev.to/nextpatch</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nextpatch"/>
    <language>en</language>
    <item>
      <title>5 cambios de Java 21 que deberías estar usando ya</title>
      <dc:creator>Nextpatch</dc:creator>
      <pubDate>Sun, 11 Oct 2026 09:04:07 +0000</pubDate>
      <link>https://dev.to/nextpatch/5-cambios-de-java-21-que-deberias-estar-usando-ya-pc9</link>
      <guid>https://dev.to/nextpatch/5-cambios-de-java-21-que-deberias-estar-usando-ya-pc9</guid>
      <description>&lt;p&gt;Si buscas algo sobre Java moderno en español, lo normal es acabar leyendo en inglés: la documentación, los JEP y casi todos los artículos lo están. Así que aquí va uno escrito en castellano desde el principio.&lt;/p&gt;

&lt;p&gt;Java 21 salió en septiembre de 2023 y muchos proyectos siguen escribiendo código como si estuvieran en Java 11. Compila igual, pero se pierden cambios que hacen el código más corto y más fácil de leer, y que te pueden preguntar en cualquier entrevista técnica.&lt;/p&gt;

&lt;p&gt;Aquí van cinco que puedes empezar a usar el lunes. Cada uno lleva un reto de dos minutos para comprobar que lo tienes claro.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Hilos virtuales: escala, no velocidad
&lt;/h2&gt;

&lt;p&gt;Un hilo de plataforma, el &lt;code&gt;Thread&lt;/code&gt; de siempre, envuelve un hilo del sistema operativo: es caro de crear y hay pocos. Por eso llevamos años con pools acotados o programación asíncrona.&lt;/p&gt;

&lt;p&gt;Java 21 cierra los &lt;strong&gt;hilos virtuales&lt;/strong&gt; (JEP 444). Siguen siendo instancias de &lt;code&gt;java.lang.Thread&lt;/code&gt;, pero los planifica la JVM sobre unos pocos hilos del sistema. Cuando uno se bloquea en una operación de E/S del JDK, se desmonta y deja libre su hilo portador para otro.&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;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newVirtualThreadPerTaskExecutor&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&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;Lo que más se malinterpreta: &lt;strong&gt;no son hilos más rápidos&lt;/strong&gt;. Lo que mejora es el throughput cuando tienes miles de tareas que pasan casi todo el tiempo esperando (peticiones HTTP, consultas a base de datos). Si tu carga es de CPU, no ganas nada.&lt;/p&gt;

&lt;p&gt;Un matiz antes de usarlos en producción con Java 21: dentro de un bloque &lt;code&gt;synchronized&lt;/code&gt; el hilo virtual queda &lt;strong&gt;anclado&lt;/strong&gt; (pinning) y bloquea también a su portador. Java 24 lo resolvió con el JEP 491, pero en Java 21 conviene cambiar a &lt;code&gt;ReentrantLock&lt;/code&gt; los &lt;code&gt;synchronized&lt;/code&gt; que envuelven llamadas de red.&lt;/p&gt;

&lt;p&gt;Practícalo: &lt;a href="https://nextpatch.dev/reto/hilos-virtuales-escala-no-velocidad-1?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=java21-5-cambios-es" rel="noopener noreferrer"&gt;hilos virtuales, escala y no velocidad&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Pattern matching en switch
&lt;/h2&gt;

&lt;p&gt;Decidir qué hacer según el tipo de un objeto era una cadena de &lt;code&gt;if ... else if&lt;/code&gt; con &lt;code&gt;instanceof&lt;/code&gt;. Java 21 cierra el &lt;strong&gt;pattern matching para switch&lt;/strong&gt; (JEP 441), después de cuatro rondas de preview desde Java 17:&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;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;formatter&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;obj&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;obj&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="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"int %d"&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="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"long %d"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"String %s"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&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;El selector puede ser de cualquier tipo de referencia, y cada &lt;code&gt;case&lt;/code&gt; lleva un patrón de tipo como los de &lt;code&gt;instanceof&lt;/code&gt;. Gana el primer &lt;code&gt;case&lt;/code&gt; que encaja, en el orden en que están escritos.&lt;/p&gt;

&lt;p&gt;Lo importante es que compara el &lt;strong&gt;tipo real&lt;/strong&gt; del objeto, no su valor: un &lt;code&gt;Object&lt;/code&gt; que guarda un &lt;code&gt;Long&lt;/code&gt; no entra en &lt;code&gt;case Integer i&lt;/code&gt;, aunque el número quepa en un &lt;code&gt;int&lt;/code&gt;. Además, el compilador te avisa si un &lt;code&gt;case&lt;/code&gt; nunca se puede alcanzar porque otro anterior ya lo cubre.&lt;/p&gt;

&lt;p&gt;Practícalo: &lt;a href="https://nextpatch.dev/reto/switch-deja-de-comparar-solo-constantes-3?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=java21-5-cambios-es" rel="noopener noreferrer"&gt;el switch deja de comparar solo constantes&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. case null y guardas when
&lt;/h2&gt;

&lt;p&gt;Antes, un &lt;code&gt;switch&lt;/code&gt; con selector &lt;code&gt;null&lt;/code&gt; lanzaba &lt;code&gt;NullPointerException&lt;/code&gt;, así que la comprobación vivía fuera, en un &lt;code&gt;if&lt;/code&gt;. Ahora puedes meterla dentro con &lt;strong&gt;&lt;code&gt;case null&lt;/code&gt;&lt;/strong&gt;, y añadir condiciones a cada caso con &lt;strong&gt;&lt;code&gt;when&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;-&amp;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="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Oops!"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equalsIgnoreCase&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"YES"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;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="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"You got it"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-&amp;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="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sorry?"&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;Una guarda solo aplica si el patrón encaja &lt;strong&gt;y&lt;/strong&gt; la condición es cierta. Si no, el &lt;code&gt;switch&lt;/code&gt; sigue probando los siguientes &lt;code&gt;case&lt;/code&gt;. El orden importa: el caso con guarda va antes que el general, porque al revés el general lo taparía y el compilador daría error.&lt;/p&gt;

&lt;p&gt;Solo los patrones admiten guardas: &lt;code&gt;case "hola" when ...&lt;/code&gt; no compila, porque una constante no es un patrón.&lt;/p&gt;

&lt;p&gt;¿Y qué pasa con el &lt;code&gt;default&lt;/code&gt; cuando llega un &lt;code&gt;null&lt;/code&gt;? Te lo dejo para el reto del final.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Record patterns
&lt;/h2&gt;

&lt;p&gt;El pattern matching de &lt;code&gt;instanceof&lt;/code&gt; te ahorra el cast, pero luego sigues escribiendo &lt;code&gt;p.x()&lt;/code&gt; y &lt;code&gt;p.y()&lt;/code&gt;. Java 21 cierra los &lt;strong&gt;record patterns&lt;/strong&gt; (JEP 440), que sacan los componentes en el propio patrón:&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;record&lt;/span&gt; &lt;span class="nf"&gt;Point&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;x&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;y&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;obj&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nf"&gt;Point&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;x&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;y&lt;/span&gt;&lt;span class="o"&gt;))&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="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&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;El patrón comprueba el tipo y, si encaja, llama a los accesores del record. Los nombres de las variables no tienen que coincidir con los de los componentes, puedes usar &lt;code&gt;var&lt;/code&gt; en cada posición y funcionan igual en &lt;code&gt;instanceof&lt;/code&gt; y en los &lt;code&gt;case&lt;/code&gt; de un &lt;code&gt;switch&lt;/code&gt;. Los patrones se pueden anidar: el conjunto solo encaja si encajan &lt;strong&gt;todos&lt;/strong&gt; los subpatrones, y si alguno falla, no hay excepción, simplemente no entra en esa rama.&lt;/p&gt;

&lt;p&gt;Combinado con un &lt;code&gt;switch&lt;/code&gt; sobre una jerarquía &lt;code&gt;sealed&lt;/code&gt;, tratas cada forma de tus datos en una línea, sin casts y con el compilador comprobando que no se te escapa ningún caso.&lt;/p&gt;

&lt;p&gt;Practícalo: &lt;a href="https://nextpatch.dev/reto/deconstruir-un-record-dentro-del-patron-3?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=java21-5-cambios-es" rel="noopener noreferrer"&gt;deconstruir un record dentro del patrón&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Colecciones secuenciadas
&lt;/h2&gt;

&lt;p&gt;Pedir el primer y el último elemento dependía del tipo: &lt;code&gt;list.get(0)&lt;/code&gt; y &lt;code&gt;list.get(list.size() - 1)&lt;/code&gt; en una lista, &lt;code&gt;deque.getFirst()&lt;/code&gt; en un &lt;code&gt;Deque&lt;/code&gt;, &lt;code&gt;sortedSet.first()&lt;/code&gt; en un &lt;code&gt;SortedSet&lt;/code&gt;... y en un &lt;code&gt;LinkedHashSet&lt;/code&gt; no había forma de sacar el último sin recorrerlo entero.&lt;/p&gt;

&lt;p&gt;El JEP 431 añade &lt;code&gt;SequencedCollection&lt;/code&gt;, &lt;code&gt;SequencedSet&lt;/code&gt; y &lt;code&gt;SequencedMap&lt;/code&gt; para las colecciones con un orden definido, y las encaja en la jerarquía que ya existía:&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;primero&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lista&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getFirst&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;ultimo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lista&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLast&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;ultimoDelSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;linkedHashSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLast&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Además de &lt;code&gt;getFirst&lt;/code&gt; y &lt;code&gt;getLast&lt;/code&gt; llegan &lt;code&gt;addFirst&lt;/code&gt;, &lt;code&gt;addLast&lt;/code&gt;, &lt;code&gt;removeFirst&lt;/code&gt;, &lt;code&gt;removeLast&lt;/code&gt; y &lt;code&gt;reversed()&lt;/code&gt;. Ojo con este último: &lt;strong&gt;no copia&lt;/strong&gt; la colección, devuelve una vista en orden inverso. Y con la colección vacía, &lt;code&gt;getFirst()&lt;/code&gt; y &lt;code&gt;getLast()&lt;/code&gt; lanzan &lt;code&gt;NoSuchElementException&lt;/code&gt;, como ya hacía &lt;code&gt;Deque&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Practícalo: &lt;a href="https://nextpatch.dev/reto/getfirst-getlast-y-reversed-3?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=java21-5-cambios-es" rel="noopener noreferrer"&gt;getFirst, getLast y reversed&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mini reto: el null y el default
&lt;/h2&gt;

&lt;p&gt;Este código tiene un &lt;code&gt;default&lt;/code&gt;, pero no tiene &lt;code&gt;case 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="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;describe&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;o&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;o&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="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"texto"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"número"&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="s"&gt;"otra cosa"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;};&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;describe&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;¿Qué pasa al llamar a &lt;code&gt;describe(null)&lt;/code&gt;? ¿Devuelve &lt;code&gt;"otra cosa"&lt;/code&gt;, lanza una excepción o ni siquiera compila?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Responde en &lt;a href="https://nextpatch.dev/reto/case-null-y-guardas-when-1?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=java21-5-cambios-es" rel="noopener noreferrer"&gt;el reto de case null y guardas when&lt;/a&gt;. No hace falta cuenta y se resuelve en un momento.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Escribo estos artículos mientras construyo &lt;a href="https://nextpatch.dev/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=java21-5-cambios-es" rel="noopener noreferrer"&gt;NextPatch&lt;/a&gt;, un hábito diario para desarrolladores Java: un resumen de 45 segundos de un cambio entre versiones y un reto de 2 minutos, también en español. ¿Qué novedad de Java 21 has adoptado de verdad en el trabajo y cuál sigues ignorando? Cuéntamelo en los comentarios.&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>java</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>5 Java 21 changes you should already be using</title>
      <dc:creator>Nextpatch</dc:creator>
      <pubDate>Thu, 08 Oct 2026 20:45:00 +0000</pubDate>
      <link>https://dev.to/nextpatch/5-java-21-changes-you-should-already-be-using-223g</link>
      <guid>https://dev.to/nextpatch/5-java-21-changes-you-should-already-be-using-223g</guid>
      <description>&lt;p&gt;ava 21 came out in September 2023, and plenty of projects still write code as if they were on Java 11. It compiles just the same, but they miss changes that make code shorter and easier to read, and that can come up in any technical interview.&lt;/p&gt;

&lt;p&gt;Here are five you can start using on Monday. Each one comes with a two-minute challenge to check you've got it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Virtual threads: scale, not speed
&lt;/h2&gt;

&lt;p&gt;A platform thread, the &lt;code&gt;Thread&lt;/code&gt; we've always had, wraps an operating system thread: it's expensive to create and there aren't many of them. That's why we've spent years with bounded pools or asynchronous code.&lt;/p&gt;

&lt;p&gt;Java 21 finalises &lt;strong&gt;virtual threads&lt;/strong&gt; (JEP 444). They are still instances of &lt;code&gt;java.lang.Thread&lt;/code&gt;, but the JVM schedules them over a few OS threads. When one blocks on a JDK I/O operation, it unmounts and frees its carrier thread for another one.&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;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newVirtualThreadPerTaskExecutor&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&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 most misunderstood part: &lt;strong&gt;they are not faster threads&lt;/strong&gt;. What improves is throughput when you have thousands of tasks that spend most of their time waiting (HTTP requests, database queries). If your workload is CPU-bound, you gain nothing.&lt;/p&gt;

&lt;p&gt;One caveat before using them in production on Java 21: inside a &lt;code&gt;synchronized&lt;/code&gt; block the virtual thread gets &lt;strong&gt;pinned&lt;/strong&gt; and blocks its carrier too. Java 24 fixed that with JEP 491, but on Java 21 it's worth switching the &lt;code&gt;synchronized&lt;/code&gt; blocks that wrap network calls to a &lt;code&gt;ReentrantLock&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Practise it: &lt;a href="https://nextpatch.dev/reto/hilos-virtuales-escala-no-velocidad-1?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=java21-5-changes" rel="noopener noreferrer"&gt;virtual threads, scale not speed&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Pattern matching for switch
&lt;/h2&gt;

&lt;p&gt;Deciding what to do based on an object's type used to be a chain of &lt;code&gt;if ... else if&lt;/code&gt; with &lt;code&gt;instanceof&lt;/code&gt;. Java 21 finalises &lt;strong&gt;pattern matching for switch&lt;/strong&gt; (JEP 441), after four preview rounds starting in Java 17:&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;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;formatter&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;obj&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;obj&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="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"int %d"&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="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"long %d"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"String %s"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&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 selector can be any reference type, and each &lt;code&gt;case&lt;/code&gt; takes a type pattern just like &lt;code&gt;instanceof&lt;/code&gt;. The first &lt;code&gt;case&lt;/code&gt; that matches wins, in the order they're written.&lt;/p&gt;

&lt;p&gt;The key point is that it compares the object's &lt;strong&gt;actual type&lt;/strong&gt;, not its value: an &lt;code&gt;Object&lt;/code&gt; holding a &lt;code&gt;Long&lt;/code&gt; doesn't match &lt;code&gt;case Integer i&lt;/code&gt;, even if the number fits in an &lt;code&gt;int&lt;/code&gt;. On top of that, the compiler tells you when a &lt;code&gt;case&lt;/code&gt; can never be reached because an earlier one already covers it.&lt;/p&gt;

&lt;p&gt;Practise it: &lt;a href="https://nextpatch.dev/reto/switch-deja-de-comparar-solo-constantes-3?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=java21-5-changes" rel="noopener noreferrer"&gt;switch stops comparing only constants&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. case null and when guards
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;switch&lt;/code&gt; with a &lt;code&gt;null&lt;/code&gt; selector used to throw &lt;code&gt;NullPointerException&lt;/code&gt;, so the check lived outside it, in an &lt;code&gt;if&lt;/code&gt;. Now you can bring it inside with &lt;strong&gt;&lt;code&gt;case null&lt;/code&gt;&lt;/strong&gt;, and add conditions to each case with &lt;strong&gt;&lt;code&gt;when&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;-&amp;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="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Oops!"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equalsIgnoreCase&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"YES"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;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="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"You got it"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-&amp;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="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sorry?"&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;A guard only applies if the pattern matches &lt;strong&gt;and&lt;/strong&gt; the condition is true. If not, the &lt;code&gt;switch&lt;/code&gt; keeps trying the following cases. Order matters: the guarded case goes before the general one, because the other way round the general one would hide it and the compiler would report an error.&lt;/p&gt;

&lt;p&gt;Only patterns take guards: &lt;code&gt;case "hello" when ...&lt;/code&gt; doesn't compile, because a constant isn't a pattern.&lt;/p&gt;

&lt;p&gt;And what happens to &lt;code&gt;default&lt;/code&gt; when a &lt;code&gt;null&lt;/code&gt; comes in? I'll leave that for the challenge at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Record patterns
&lt;/h2&gt;

&lt;p&gt;Pattern matching for &lt;code&gt;instanceof&lt;/code&gt; saves you the cast, but you still end up writing &lt;code&gt;p.x()&lt;/code&gt; and &lt;code&gt;p.y()&lt;/code&gt;. Java 21 finalises &lt;strong&gt;record patterns&lt;/strong&gt; (JEP 440), which pull the components out in the pattern itself:&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;record&lt;/span&gt; &lt;span class="nf"&gt;Point&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;x&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;y&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;obj&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nf"&gt;Point&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;x&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;y&lt;/span&gt;&lt;span class="o"&gt;))&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="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&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 pattern checks the type and, if it matches, calls the record's accessors. The variable names don't have to match the component names, you can use &lt;code&gt;var&lt;/code&gt; in each position, and they work the same in &lt;code&gt;instanceof&lt;/code&gt; and in the &lt;code&gt;case&lt;/code&gt; labels of a &lt;code&gt;switch&lt;/code&gt;. Patterns can be nested: the whole thing only matches if &lt;strong&gt;all&lt;/strong&gt; the subpatterns match, and if one fails there's no exception, the branch just doesn't run.&lt;/p&gt;

&lt;p&gt;Combined with a &lt;code&gt;switch&lt;/code&gt; over a &lt;code&gt;sealed&lt;/code&gt; hierarchy, you handle each shape of your data in one line, with no casts, and the compiler checks that no case slips through.&lt;/p&gt;

&lt;p&gt;Practise it: &lt;a href="https://nextpatch.dev/reto/deconstruir-un-record-dentro-del-patron-3?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=java21-5-changes" rel="noopener noreferrer"&gt;deconstructing a record inside the pattern&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Sequenced collections
&lt;/h2&gt;

&lt;p&gt;Getting the first and last element depended on the type: &lt;code&gt;list.get(0)&lt;/code&gt; and &lt;code&gt;list.get(list.size() - 1)&lt;/code&gt; on a list, &lt;code&gt;deque.getFirst()&lt;/code&gt; on a &lt;code&gt;Deque&lt;/code&gt;, &lt;code&gt;sortedSet.first()&lt;/code&gt; on a &lt;code&gt;SortedSet&lt;/code&gt;... and on a &lt;code&gt;LinkedHashSet&lt;/code&gt; there was no way to get the last one without walking through the whole set.&lt;/p&gt;

&lt;p&gt;JEP 431 adds &lt;code&gt;SequencedCollection&lt;/code&gt;, &lt;code&gt;SequencedSet&lt;/code&gt; and &lt;code&gt;SequencedMap&lt;/code&gt; for collections with a defined order, and fits them into the existing hierarchy:&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;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getFirst&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;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLast&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;lastInSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;linkedHashSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLast&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Besides &lt;code&gt;getFirst&lt;/code&gt; and &lt;code&gt;getLast&lt;/code&gt; you get &lt;code&gt;addFirst&lt;/code&gt;, &lt;code&gt;addLast&lt;/code&gt;, &lt;code&gt;removeFirst&lt;/code&gt;, &lt;code&gt;removeLast&lt;/code&gt; and &lt;code&gt;reversed()&lt;/code&gt;. Careful with that last one: it &lt;strong&gt;doesn't copy&lt;/strong&gt; the collection, it returns a view in reverse order. And on an empty collection, &lt;code&gt;getFirst()&lt;/code&gt; and &lt;code&gt;getLast()&lt;/code&gt; throw &lt;code&gt;NoSuchElementException&lt;/code&gt;, just as &lt;code&gt;Deque&lt;/code&gt; already did.&lt;/p&gt;

&lt;p&gt;Practise it: &lt;a href="https://nextpatch.dev/reto/getfirst-getlast-y-reversed-3?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=java21-5-changes" rel="noopener noreferrer"&gt;getFirst, getLast and reversed&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mini challenge: null and default
&lt;/h2&gt;

&lt;p&gt;This code has a &lt;code&gt;default&lt;/code&gt;, but no &lt;code&gt;case 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="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;describe&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;o&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;o&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="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"number"&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="s"&gt;"something else"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;};&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;describe&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens when you call &lt;code&gt;describe(null)&lt;/code&gt;? Does it return &lt;code&gt;"something else"&lt;/code&gt;, throw an exception, or not even compile?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Answer it in &lt;a href="https://nextpatch.dev/reto/case-null-y-guardas-when-1?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=java21-5-changes" rel="noopener noreferrer"&gt;the case null and when guards challenge&lt;/a&gt;. No account needed, and it only takes a moment.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;I write these while building &lt;a href="https://nextpatch.dev/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=java21-5-changes" rel="noopener noreferrer"&gt;NextPatch&lt;/a&gt;, a daily habit for Java developers: a 45-second summary of one change between versions and a 2-minute challenge. Which Java 21 feature did you actually adopt at work, and which one are you still ignoring? Tell me in the comments.&lt;/p&gt;

</description>
      <category>java</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>career</category>
    </item>
    <item>
      <title>I made every Java challenge in my app a public link you can solve without signing up. Roast the setup?</title>
      <dc:creator>Nextpatch</dc:creator>
      <pubDate>Sun, 04 Oct 2026 09:59:26 +0000</pubDate>
      <link>https://dev.to/nextpatch/i-made-every-java-challenge-in-my-app-a-public-link-you-can-solve-without-signing-up-roast-the-45dh</link>
      <guid>https://dev.to/nextpatch/i-made-every-java-challenge-in-my-app-a-public-link-you-can-solve-without-signing-up-roast-the-45dh</guid>
      <description>&lt;p&gt;A few weeks ago I posted about&lt;br&gt;
&lt;a href="https://nextpatch.dev/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=post2" rel="noopener noreferrer"&gt;NextPatch&lt;/a&gt;,&lt;br&gt;
a small daily habit for Java developers: a 45-second summary of one change between&lt;br&gt;
versions, then a 2-minute challenge.&lt;/p&gt;

&lt;p&gt;Thanks for the feedback on that one. Re-reading the post afterwards, one thing bothered&lt;br&gt;
me: every link ended on a login screen. I was asking people to create an account&lt;br&gt;
before they'd seen a single challenge. I wouldn't do that for a quiz app either.&lt;/p&gt;

&lt;p&gt;So I spent the last few weeks on one idea:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every challenge should be a link you can share, open on your phone and solve in&lt;br&gt;
under 30 seconds, without an account.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's how I built it. I'd really like your opinion on the design, and on how it&lt;br&gt;
&lt;em&gt;feels&lt;/em&gt; if you try one.&lt;/p&gt;

&lt;p&gt;-&amp;gt; &lt;strong&gt;Today's challenge:&lt;/strong&gt; &lt;a href="https://nextpatch.dev/reto-de-hoy?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=post2" rel="noopener noreferrer"&gt;nextpatch.dev/reto-de-hoy&lt;/a&gt;&lt;br&gt;
-&amp;gt; &lt;strong&gt;All of them:&lt;/strong&gt; &lt;a href="https://nextpatch.dev/retos?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=post2" rel="noopener noreferrer"&gt;nextpatch.dev/retos&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Every challenge gets a URL that never changes
&lt;/h2&gt;

&lt;p&gt;Challenges don't have titles, so the slug is built from the lesson title plus the&lt;br&gt;
challenge's position in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/reto/var-adios-a-repetir-el-tipo-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a collision it appends &lt;code&gt;-2&lt;/code&gt;, &lt;code&gt;-3&lt;/code&gt;... The important part is that &lt;strong&gt;the slug is set&lt;br&gt;
when the challenge is created and never recalculated&lt;/strong&gt;, even if someone renames the&lt;br&gt;
lesson or reorders it. A link someone shared on WhatsApp six months ago still has to&lt;br&gt;
work.&lt;/p&gt;

&lt;p&gt;Production already had ~560 challenges, so a startup runner fills in the missing slugs.&lt;br&gt;
It's idempotent: once every challenge has one, it does nothing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Yes, the URLs are in Spanish. That's where the project started. Is it weird for you&lt;br&gt;
as an English speaker? Honest question.)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Grading without an account, and without leaking the answer
&lt;/h2&gt;

&lt;p&gt;The page calls two anonymous endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET  /api/v1/public/challenges/{slug}        → the challenge, no solution
POST /api/v1/public/challenges/{slug}/check  → { answer } → { correct, explanation }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each challenge stores a &lt;code&gt;payload&lt;/code&gt; (what you see) and a &lt;code&gt;solution&lt;/code&gt; (what the server&lt;br&gt;
checks), and the solution is never serialized. Grading is deterministic: the server&lt;br&gt;
compares option ids, tokens or line orders. &lt;strong&gt;No user code is ever executed.&lt;/strong&gt; No&lt;br&gt;
sandbox, no containers, so it all runs fine on a small home server.&lt;/p&gt;

&lt;p&gt;The anonymous &lt;code&gt;check&lt;/code&gt; doesn't save anything and doesn't give XP. It just tells you&lt;br&gt;
whether you were right and why.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Solve first, sign up later, and keep the credit
&lt;/h2&gt;

&lt;p&gt;This is the part I'm least sure about. I'd love your take.&lt;/p&gt;

&lt;p&gt;If you get it right &lt;strong&gt;without&lt;/strong&gt; being logged in, the server sets a cookie:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;np_reto = slug.epochSeconds.HMAC-SHA256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's signed so you can't forge "I solved 50 challenges", it's &lt;code&gt;HttpOnly&lt;/code&gt; and &lt;code&gt;Secure&lt;/code&gt;,&lt;br&gt;
and it expires after 24 hours. Only &lt;em&gt;after&lt;/em&gt; you get it right does the page show "Keep&lt;br&gt;
your streak: create an account". It never asks before.&lt;/p&gt;

&lt;p&gt;If you sign up or log in while that cookie is alive, the challenge is &lt;strong&gt;claimed&lt;/strong&gt;: it&lt;br&gt;
counts as solved, you get its XP, and today counts as day 1 of your streak. Claiming is&lt;br&gt;
idempotent: if you'd already solved it, nothing happens. And it doesn't unlock the&lt;br&gt;
lesson. You still walk the path in order.&lt;/p&gt;

&lt;p&gt;Why a cookie and not a row in the database? The server doesn't create any record for&lt;br&gt;
anonymous visitors. Your progress only exists on the server once you decide to sign up.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. A "challenge of the day" that's the same for everyone
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/reto-de-hoy&lt;/code&gt; redirects to one challenge, the same for everyone that day. It's picked&lt;br&gt;
deterministically from the date:&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;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;choose&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LocalDate&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;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Candidate&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;easy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;difficulty&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="no"&gt;QUICK_TYPES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;easy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;easy&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;pool&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="nc"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;floorMod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scramble&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toEpochDay&lt;/span&gt;&lt;span class="o"&gt;()),&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;())).&lt;/span&gt;&lt;span class="na"&gt;slug&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;Two details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It only picks &lt;strong&gt;easy, quick types&lt;/strong&gt; (multiple choice, fill in the blank, predict the
output). The daily challenge is a first impression, not a boss fight.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scramble&lt;/code&gt; is the SplitMix64 finalizer. With the raw day number, consecutive days
would land on neighbouring challenges from the same lesson. Mixing the bits spreads
them across the whole catalog.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The choice is cached in memory until the date changes. Publishing new content&lt;br&gt;
mid-morning doesn't change today's challenge for someone who has already seen it.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Link previews drawn with Java2D
&lt;/h2&gt;

&lt;p&gt;A shared link is only as good as its preview. Every challenge has an Open Graph image&lt;br&gt;
(1200×630): the code with syntax highlighting and the &lt;strong&gt;blank drawn as an empty box&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I didn't want a headless browser on a small server, so it's plain &lt;strong&gt;Java2D&lt;/strong&gt; with the&lt;br&gt;
fonts bundled in the jar. Two decisions I like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The renderer only gets the same DTO as the public page, so the answer &lt;em&gt;can't&lt;/em&gt; end up
in the image.&lt;/li&gt;
&lt;li&gt;The image URL carries a hash of the challenge's content. If the challenge changes, the
URL changes, and LinkedIn, WhatsApp and the CDN fetch the new image. If it doesn't, the
image is drawn once and served from cache.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. The test I set before promoting anything
&lt;/h2&gt;

&lt;p&gt;I set myself one rule before posting anywhere: &lt;em&gt;someone who opens a shared link on their&lt;br&gt;
phone, with no account, solves a challenge in under 30 seconds.&lt;/em&gt; The first time I tried,&lt;br&gt;
on mobile, the challenge started about 1,230 px down the page, below the theory.&lt;br&gt;
Now it's right under the title and the explanation is folded behind "Why? See the&lt;br&gt;
explanation". My last run took &lt;strong&gt;3.6 seconds&lt;/strong&gt; from tapping the link to "Correct!".&lt;/p&gt;

&lt;p&gt;That was me, though, and I know where everything is. That's why I'm writing this.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd love from you
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;On the design:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Solve-first, sign-up-later with a signed cookie: would you do it differently?&lt;/li&gt;
&lt;li&gt;Slugs that never change, even when the title does: right call, or will it bite me?&lt;/li&gt;
&lt;li&gt;Java2D for link previews instead of a headless browser: clever or cursed?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;On the experience&lt;/strong&gt; (if you have 2 minutes):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://nextpatch.dev/reto-de-hoy?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=post2" rel="noopener noreferrer"&gt;today's challenge&lt;/a&gt;
on your &lt;strong&gt;phone&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Solve it, or fail it. Both are useful to me.&lt;/li&gt;
&lt;li&gt;Tell me in the comments: &lt;strong&gt;how long did it take, what slowed you down, and did
the explanation help?&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Anything that felt slow, confusing or ugly is exactly what I want to hear.&lt;br&gt;
Thanks for reading :)&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>java</category>
      <category>springboot</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I got tired of always being three Java versions behind, so I built a little game to fix it</title>
      <dc:creator>Nextpatch</dc:creator>
      <pubDate>Fri, 25 Sep 2026 21:48:45 +0000</pubDate>
      <link>https://dev.to/nextpatch/i-got-tired-of-always-being-three-java-versions-behind-so-i-built-a-little-game-to-fix-it-3nn</link>
      <guid>https://dev.to/nextpatch/i-got-tired-of-always-being-three-java-versions-behind-so-i-built-a-little-game-to-fix-it-3nn</guid>
      <description>&lt;p&gt;Java ships a new version every six months. I kept telling myself I'd read the release notes "someday".&lt;/p&gt;

&lt;p&gt;Then I found myself in an interview, staring at a &lt;code&gt;switch&lt;/code&gt; with pattern matching, and realised I was &lt;strong&gt;three versions behind&lt;/strong&gt; and didn't even know it.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://nextpatch.dev" rel="noopener noreferrer"&gt;NextPatch&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;A tiny daily habit for working Java developers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read a ~45-second summary&lt;/strong&gt; of one change between versions, with a before/after example.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solve a ~2-minute challenge&lt;/strong&gt;: fill the blank, fix the bug, order the lines or predict the output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Earn XP and keep your streak.&lt;/strong&gt; Come back tomorrow.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Think language-learning app, but for not going rusty as a developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try one right now (no sign-up)
&lt;/h2&gt;

&lt;p&gt;Since Java 22 you can mark a variable you have to declare but won't use with a single character. Fill in the blank:&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;int&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="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;Order&lt;/span&gt; &lt;span class="err"&gt;❓&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;total&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;Answer&lt;/p&gt;

&lt;p&gt;&lt;code&gt;_&lt;/code&gt;, the &lt;strong&gt;unnamed variable&lt;/strong&gt; (JEP 456). It says "I need the variable, not its value". It works in loops, in &lt;code&gt;catch (Exception _)&lt;/code&gt;, in lambdas and in record patterns.&lt;/p&gt;

&lt;p&gt;There are three of these on the landing page that you can solve without an account.&lt;/p&gt;

&lt;p&gt;What's included&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Paths by version jump: Java 8 → 11, 17 → 21, 21 → 25, with more to come as new versions ship&lt;/li&gt;
&lt;li&gt;Challenge types: fill the blank, multiple choice, fix the bug, order lines, predict the output&lt;/li&gt;
&lt;li&gt;XP, daily streaks, badges, and weekly + global leaderboards&lt;/li&gt;
&lt;li&gt;Content in English, Spanish and Catalan&lt;/li&gt;
&lt;li&gt;Free. No card, no ads, no trial that runs out. The free account is the full account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few things I learned building it&lt;/p&gt;

&lt;p&gt;Since this is dev.to, here's the part under the hood:&lt;/p&gt;

&lt;p&gt;No code sandbox, on purpose. Running untrusted user code safely is a whole product in itself. Instead, every challenge is graded deterministically on the server: token matching, string validation or option checking. The solution is never sent to the client. It keeps grading instant and the infrastructure tiny.&lt;/p&gt;

&lt;p&gt;Eating my own dog food on the stack. It runs on Java 25 + Spring Boot 4 with virtual threads turned on (spring.threads.virtual.enabled=true). The workload is almost all I/O, which is exactly where Loom pays off.&lt;/p&gt;

&lt;p&gt;Boring architecture, happily. It's a Thymeleaf monolith with server-side sessions (form login + Google OIDC + remember-me), plus a small JSON API called with fetch. No SPA, no JWT for auth. For a solo project that has been a real productivity win.&lt;/p&gt;

&lt;p&gt;It runs on a Raspberry Pi. Production sits on a Pi at home, published through a Cloudflare Tunnel. No open ports and no cloud bill so far.&lt;/p&gt;

&lt;p&gt;i18n of content is harder than i18n of UI. Translating a challenge must change the words and never its shape: same number of options, same order, same blanks. Otherwise it grades wrong without throwing any error. Lesson learned.&lt;/p&gt;

&lt;p&gt;It's in beta&lt;/p&gt;

&lt;p&gt;Things may break. If you try it, I'd genuinely love feedback:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which Java version jump should I cover next?&lt;/li&gt;
&lt;li&gt;Is 3 minutes a day the right size, or too little?&lt;/li&gt;
&lt;li&gt;Which challenge type do you like most, and which least?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;--&amp;gt; &lt;a href="https://nextpatch.dev" rel="noopener noreferrer"&gt;NextPatch&lt;/a&gt;, free and all features included.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>java</category>
    </item>
  </channel>
</rss>
