At technical unconferences I like to start (and conclude) the day with some fun coding. After all, writing code is (still, in June 2025,) at the heart of software development. During SoCraTes Linz 2024 I proposed the first session to be a "Coding Fun session: Only use exceptions for control flow." I explained that it would be impractical but fun ;-) Using exceptions like that was an anti pattern but I wanted to see what would be possible.
Coding Fun: Only Exceptions for Control Flow
I start the session with a few slides: Control flow is is the order in which individual statements, instructions or function calls of an imperative program are executed or evaluated. And the idea of the session is to only use exceptions for changing the control flow. This is a constraint, an artificial addition to a coding exercise, to make it more focused or challenging. To force us to use exceptions for control flow I need to take away all other options:
- Not using
if, while
orfor
, the ternary operator or similar language constructs. (This is the same as the constraint Cyclomatic Complexity One.) - Not using any array or (Java) stream operations like
map, filter
, etc. (These functions are a way to deal with Cyclomatic Complexity One.) - Not using lookup arrays of functions. For example a map with the keys
true
andfalse
and functions as values is anif-else
statement. (These lookups are another way to deal with Cyclomatic Complexity One.) - Not using modulo, signum, minimum and maximum Math functions as well as various String translate and replace functions. (These are unrelated to Cyclomatic Complexity One, but offer ways to work around in the specified task further down.)

This is an exercise, even more it is an "inverted" exercise because it wants us to do things we usually avoid doing. And for good reason, using exceptions this way is an anti pattern. It is basically a
goto
statement, not a structured break
or continue
style go to, but a real nasty one where we can jump across multiple stack frames. It makes code hard to understand. Often performance is bad due to the cost of creating exceptions. Don't do this in your project. Keep in mind that we are just playing...Assignment
As a task let us create a ROT-13 "Encryption", also known as Caesar-Chiffre. Our requirements are:
- Write a function
encode
to encode a message using ROT-13. - Each letter is replaced with one 13 added in the alphabet.
- At the end of the alphabet, it continues at the beginning.
- Characters other than letters are not encoded.
- Lowercase letters are first changed to uppercase letters.
- Umlauts are letter combinations for encoding, e.g. "Ä" becomes "AE" before encoding and so on.
- Bonus: Make rotation configurable. Now it is 13.
Try it Yourself
Below I will show Java code of the most common solution. After you see it, you will not be able to un-see it. If you want to explore the constraint and the exercise on your own, then stop reading now! I encourage you to do that. Best way is to work with difficult constraints is to code a little bit and then step back and verify if all rules still apply.
Hands On!
I run this session as "interactive demo", which means I let participants decide what to do next, or at least I let them propose what to do next and I wait until a proposition is aligned with my plan. I make sure we stay focused on the main goal of the exercise. The whole coding takes 30 to 40 minutes. Further more I (demand to) use TDD and try to make the code readable (as possible). How do I start? I start with a (simple) failing test of course:
public class Rot13Test { @Test public void empty() { assertEquals("", encode("")); } }To keep things simple I add the production code (that is the function
encode
) at the top of the test class:public class Rot13Test { public String encode(String message) { return ""; }The first test established the method with the required signature, see requirement #1. Now I am able to replace letters with one 13 added in the alphabet. A suitable test case is "A" becomes "N", i.e.
assertEquals("N", encode("A"))
. The first challenge is how to check if there are characters in the message? I cannot loop or map them. How about:public class Rot13Test { public String encode(String message) { try { message.charAt(0); return "N"; } catch (StringIndexOutOfBoundsException empty) { return ""; } }Ok. Next I choose to go for more letters. I could also do that at the end, it depends what people say during the workshop... A suitable test case is "AA" becomes "NN".
public class Rot13Test { public String encode(String message) { try { char first = message.charAt(0); String remaining = message.substring(1); return encodeChar(first) + encode(remaining); } catch (StringIndexOutOfBoundsException empty) { return ""; } } private String encodeChar(char c) { return "N"; }The recursion ends when the message is empty. Back to requirement #2. My next test case is "B" becomes "M" and I only need to modify the method
encodeChar()
:public class Rot13Test { ... private String encodeChar(char c) { int encoded = c + 13; return Character.toString((char) encoded); }This works for the letters "A" to "M" (which becomes "Z"). Nice. Requirement #3 deals with letters "N" to "Z", so
public class Rot13Test { ... private String encodeChar(char c) { int encoded = c + 13; try { // if encoded <= 'Z' then jump int tmp = 1 / (encoded / ('Z' + 1)); encoded -= 26; } catch (ArithmeticException smaller) { // leave encoded alone } return Character.toString((char) encoded); }I will explain how that works: If
encoded
is less or equal to "Z" the logic is already correct. How can I create an exception if a number is less than 'Z' + 1
? If encoded
is smaller than this limit, the integer division will be zero which will result in a division by zero, which in Java causes an ArithmeticException
. If encoded
is larger than "Z", the result of the division will be 1, so dividing by it is allowed and the code continues. Now the algorithm works for all letters.
!"#$%&'()*+,-./0123456789:;<=>? @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ `abcdefghijklmnopqrstuvwxyz{|}~shows the special characters. There are three ranges which I need to handle. (At the moment there are only two ranges, and I know that lowercase letters will be handled later.) I add a test for several special characters below, i.e.
assertEquals("!/?@", encode("!/?@"))
and later one above i.e. assertEquals("[_`{~", encode("[_`{~"))
, always using the first and last character of each range.public class Rot13Test { ... private String encodeChar(char c) { int encoded = c; try { skipIfBelow(encoded, 'A'); skipIfBelow('Z', encoded); encoded += 13; skipIfBelow(encoded, 'Z' + 1); encoded -= 26; } catch (ArithmeticException smaller) { // leave encoded alone } return Character.toString((char) encoded); } private void skipIfBelow(int codePoint, int limit) { int tmp = 1 / (codePoint / limit); }By extracting a helper method
skipIfBelow()
I can check against the limits of the two ranges: Below and above. The basic algorithm is complete. Requirement #5 is more of the same, adding a toUppercase
:public class Rot13Test { ... private int toUppercase(int codePoint) { try { skipIfBelow(codePoint, 'a'); skipIfBelow('z', codePoint); return codePoint - 'a' + 'A'; } catch (ArithmeticException skipped) { // leave char alone return codePoint; } }Control Flow "Library"
I said I try to make the code more readable. This can be done with custom exceptions and extracting the flow control logic into separate "library" functions like
skipIfBelow()
. During a different run of the session I ended up withpublic class Rot13Test { ... static class SmallerThan extends Exception { public SmallerThan(Throwable cause) { super(cause); } } private void testIfSmaller(int codePoint, int limit) throws SmallerThan { try { int tmp = 1 / (codePoint / limit); } catch (ArithmeticException e) { throw new SmallerThan(e); } } static class LargerThan extends Exception { public LargerThan(Throwable cause) { super(cause); } } private void testIfLarger(int codePoint, int limit) throws LargerThan { try { testIfSmaller(limit, codePoint); } catch (SmallerThan e) { throw new LargerThan(e); } } static class ReplacedUmlaut extends Exception { public final String replacement; public ReplacedUmlaut(String replacement) { this.replacement = replacement; } } private void replaceUmlaut(int codePoint, int umlaut, String replacement) throws ReplacedUmlaut { try { testIfSmallerThan(codePoint, umlaut); testIfLargerThan(codePoint, umlaut); throw new ReplacedUmlaut(replacement); } catch (SmallerThan | LargerThan notUmlaut) { } }This allows me to distinguish different cases in the
catch
clause, which becomes a switch
statement.Closing Circle
This code is not like the code you see every day and it was definitely not the code you should write, but all participants agreed that they had a lot of fun. Several people said that I had screwed their mind and my long time peer Bernhard Woditschka said that he liked how it forced him to think outside of the box. This is probably because I am exploring negative aspects, i.e. concepts we usually try to avoid. Samuel Ytterbrink agreed saying "I think this session was like balm for the soul, nothing so freeing as being ordered to do something you are not supposed to do."
Using only exceptions for control flow, like naming all identifiers randomly, focuses on unwanted qualities of code, sharpens our awareness and (hopefully) raises our dislike of them. Ant it is a fun exercise, too. Stay tuned for my next Coding Fun session: No Composition Only Inheritance ;-)