Friday, May 1, 2026

Untyped case statement

Yet another blog post reminding how useful a compiler could be. Even for projects mainly using typed languages, it is often required to write some code with at least optional types like Typescript/Javascript or Groovy. For the latter of them quite good use case could be to have a jenkins pipeline implemented. This way I have spent some time just having to read more carefully, as what you see may be not exactly how actually things work. The code looked like this:


def aStr = 'x'
switch (aStr) {
  case 'x' || 'y':
    println('x or y')
    break;
  default:
    println('default')
    break;
}

At first glance, it looks like it should match when aStr is either 'x' or 'y'. Right?

Wrong.

The output is:


default

What actually happens?

Similar piece of code landend in my jenkins and I've spent hours trying to find the bug in a code that looks quite good.

The expression 'x' || 'y' doesn't create an "or" condition for the case statement. Instead, it evaluates to a boolean true (since both strings are truthy in Groovy). So the case statement effectively becomes:


case true:

And since aStr is the string 'x', not the boolean true, it never matches. It will always fall through to the default case.

Interestingly, JavaScript has a similar gotcha, though with a twist. In JavaScript, 'x' || 'y' evaluates to 'x' (the first truthy value), so the case becomes case 'x':. This would actually match when aStr is 'x', printing "x or y". But when aStr is 'y', it falls through to default. So the "or" logic still doesn't work as intended - it only matches one of the values. Different language, different behavior, same trap.

Testing with 'y' gives the same result:


def aStr = 'y'
switch (aStr) {
  case 'x' || 'y':
    println('x or y')
    break;
  default:
    println('default')
    break;
}
// Output: default

The correct way

Use a regular expression pattern instead:


def aStr = 'y'
switch (aStr) {
  case ~/x|y/:
    println('expr x or y')
    break;
  default:
    println('default')
    break;
}
// Output: expr x or y

Or use multiple case statements:


switch (aStr) {
  case 'x':
  case 'y':
    println('x or y')
    break;
  default:
    println('default')
    break;
}

Summary

Dynamic typing gives you flexibility, but it won't catch these subtle logical errors at compile time. What looks like an "or" condition is actually a boolean expression that evaluates before the case comparison even happens.

A typed language with proper pattern matching would have caught this at compile time. Or at least made you think twice about what you're actually expressing.

Sunday, April 19, 2026

File upload in Quarkus

One constant thing in IT is that changes are getting quicker and quicker.
People who work in this business won't get bored. Definitely good place to be.

Recently I have been trying to implement the file upload functionality with Quarkus.
While the framework is building on a well established standards dated back to J2EE standard, the recent APIs are changing - and this happened quite often. The ecosystem has evolved through several REST implementations - RESTEasy Classic, RESTEasy Reactive, and now Quarkus REST (which is essentially RESTEasy Reactive rebranded).

What I have found was: the great post and video from Sebastian - explainging all the details, despite the update post are no longer the way Quarkus file upload works.

So let's revise the topic with some (again maybe in Quarkus wolrd not most shiny and recent - at the time you are reading this) but stable API: MultipartFormDataInput. It's actually the recommended approach when you need fine-grained control over multipart request parsing.

@Slf4j
@EndpointPath("/upload")
class UploadEndpoint {


    @POST
    Response upload(MultipartFormDataInput input) {

        var metadata = metaDataIfNotFile(input.values)

        var files = extractFiles(input.values, metadata)

        files
            .forEach { write(it) }

        Response.status(201).entity(metadata).build()

    }


    def write(DataItem data) {
        var uploads = Paths.get("./my-uploads")
        var path = uploads.resolve(data.getFilename())
        try {
            if(!Files.exists(uploads)) {
                Files.createDirectories(uploads)
            }
            Files.write(path, data.data)
        } catch (e) {
            e.printStackTrace()
        }
    }


    static def metaDataIfNotFile(Map<String, Collection<FormValue>> formData) {
        formData
            .findAll { it.value.every { !it.fileItem } }
            .collectEntries { k,  formValues ->
                [ (k): formValues.collect { it.value } ] }
    }


    def extractFiles(Map<String, Collection<FormValue>> values, metadata) {
            values
                .findAll { it.value.find { it.fileItem } }
                .collectMany { extractData(it, metadata) }
    }

    def extractData(Map.Entry<String, Collection<FormValue>> entry, meta) {
            entry.value
                .findAll { it.fileItem && it.fileName?.trim() }
                .collectMany { tryData(it, meta) }
    }

    def tryData(FormValue formValue, meta) {
        try {
            [
                [
                    data    : Files.readAllBytes(formValue.getFileItem().file),
                    filename: formValue.fileName,
                    metaData: meta
                ] as DataItem
            ]
        } catch (e) {
            e.printStackTrace()
           []
        }
    }
}

@ToString @EqualsAndHashCode
class DataItem {
    byte[] data
    String filename
    Map<String, Collection<String>> metaData
}

You can test it with curl:

curl -X POST http://localhost:8080/upload -F "file=@/path/to/file"

Files land in `./my-uploads/` directory.

The full working example can be found in my github.


Quark - a kind of white cheese from Germany. 
Source: wikipedia, authored by Elskeletto