About Lukas Eder

Lukas is a Java and SQL enthusiast developer. He created the Data Geekery GmbH. He is the creator of jOOQ, a comprehensive SQL library for Java, and he is blogging mostly about these three topics: Java, SQL and jOOQ.

Rare Uses of a “ControlFlowException”

Control flows are a “relict” from imperative programming, which has leaked into various other programming paradigms, including Java’s object oriented paradigm. Apart from the useful and ubiquitous branch and loop structures, there are also primitives (e.g. GOTO) and non-locals (e.g. exceptions). Let’s have a closer look at these controversial control flow techniques.

GOTO

goto is a reserved word in the Java language. goto is also a valid instruction in JVM bytecode. Yet, in Java, it isn’t easily possible to peform goto operations. One example taken from this Stack Overflow question can be seen here:

Jumping forward

label: {
    // do stuff
    if (check) break label;
    // do more stuff
}

In bytecode:

2  iload_1 [check]
    3  ifeq 6          // Jumping forward
    6  ..

Jumping backward

label: do {
    // do stuff
    if (check) continue label;
    // do more stuff
    break label;
} while(true);

In bytecode:

2  iload_1 [check]
     3  ifeq 9
     6  goto 2          // Jumping backward
     9  ..

Of course, these tricks are useful only in very very rare occasions, and even then, you might want to re-consider. Because we all know what happens when we use goto in our code:

Drawing taken from xkcd: http://xkcd.com/292/

Breaking out of control flows with exceptions

Exceptions are a good tool to break out of a control flow structure in the event of an error or failure. But regular jumping downwards (without error or failure) can also be done using exceptions:

try {
    // Do stuff
    if (check) throw new Exception();
    // Do more stuff
}
catch (Exception notReallyAnException) {}

This feels just as kludgy as the tricks involving labels, mentioned before.

Legitimate uses of exceptions for control flow:

However, there are some other very rare occasions, where exceptions are a good tool to break out of a complex, nested control flow (without error or failure). This may be the case when you’re parsing an XML document using a SAXParser. Maybe, your logic is going to test the occurrence of at least three <check/> elements, in case of which you may want to skip parsing the rest of the document. Here is how to implement the above:

Create a ControlFlowException:

package com.example;

public class ControlFlowException 
extends SAXException {}

Note that usually, you might prefer a RuntimeException for this, but the SAX contracts require handler implementations to throw SAXException instead.

Use that ControlFlowException in a SAX handler:

package com.example;

import java.io.File;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;

public class Parse {
  public static void main(String[] args) 
  throws Exception {
    SAXParser parser = SAXParserFactory
        .newInstance()
        .newSAXParser();

    try {
      parser.parse(new File("test.xml"),
          new Handler());
      System.out.println(
          "Less than 3 <check/> elements found.");
    } catch (ControlFlowException e) {
      System.out.println(
          "3 or more <check/> elements found.");
    }
  }

  private static class Handler 
  extends DefaultHandler {

    int count;

    @Override
    public void startElement(
        String uri, 
        String localName, 
        String qName,
        Attributes attributes) {

      if ("check".equals(qName) && ++count >= 3)
        throw new ControlFlowException();
    }
  }
}

When to use exceptions for control flow:

The above practice seems reasonable with SAX, as SAX contracts expect such exceptions to happen, even if in this case, they’re not exceptions but regular control flow. Here are some indications about when to use the above practice in real world examples:

  • You want to break out of a complex algorithm (as opposed to a simple block).
  • You can implement “handlers” to introduce behaviour into complex algorithms.
  • Those “handlers” explicitly allow throwing exceptions in their contracts.
  • Your use case does not pull the weight of actually refactoring the complex algorithm.

A real-world example: Batch querying with jOOQ

In jOOQ, it is possible to “batch store” a collection of records. Instead of running a single SQL statement for every record, jOOQ collects all SQL statements and executes a JDBC batch operation to store them all at once.

As each record encapsulates its generated SQL rendering and execution for a given store() call in an object-oriented way, it would be quite tricky to extract the SQL rendering algorithm in a reusable way, without breaking (or exposing) too many things. Instead, jOOQ’s batch operation implements this simple pseudo-algorithm:

// Pseudo-code attaching a "handler" that will
// prevent query execution and throw exceptions
// instead:
context.attachQueryCollector();

// Collect the SQL for every store operation
for (int i = 0; i < records.length; i++) {
  try {
    records[i].store();
  }

  // The attached handler will result in this
  // exception being thrown rather than actually
  // storing records to the database
  catch (QueryCollectorException e) {

    // The exception is thrown after the rendered
    // SQL statement is available
    queries.add(e.query());                
  }
}

A real-world example: Exceptionally changing behaviour

Another example from jOOQ shows how this technique can be useful to introduce exceptional behaviour that is applicable only in rare cases. As explained in issue #1520, some databases have a limitation regarding the number of possible bind values per statement. These are:

  • SQLite: 999
  • Ingres 10.1.0: 1024
  • Sybase ASE 15.5: 2000
  • SQL Server 2008: 2100

In order to circumvent this limitation, it will be necessary for jOOQ to inline all bind values, once the maximum has been reached. As jOOQ’s query model heavily encapsulates SQL rendering and variable binding behaviour by applying the composite pattern, it is not possible to know the number of bind values before traversing a query model tree. For more details about jOOQ’s query model architecture, consider this previous blog post: http://blog.jooq.org/2012/04/10/the-visitor-pattern-re-visited

So the solution is to render the SQL statement and count bind values that are effectively going to be rendered. A canonical implementation would be this pseudo code:

String sql;

query.renderWith(countRenderer);
if (countRenderer.bindValueCount() > maxBindValues) {
  sql = query.renderWithInlinedBindValues();
}
else {
  sql = query.render();
}

As can be seen, a canonical implementation will need to render the SQL statement twice. The first rendering is used only to count the number of bind values, whereas the second rendering will generate the true SQL statement. The problem here is that the exceptional behaviour should only be put in place, once the exceptional event (too many bind values) occurs. A much better solution is to introduce a “handler” that counts bind values in a regular “rendering attempt”, throwing a ControlFlowException for those few exceptional “attempts” where the number of bind values exceeds the maximum:

// Pseudo-code attaching a "handler" that will
// abort query rendering once the maximum number
// of bind values was exceeded:
context.attachBindValueCounter();
String sql;
try {

  // In most cases, this will succeed:
  sql = query.render();
}
catch (ReRenderWithInlinedVariables e) {
  sql = query.renderWithInlinedBindValues();
}

The second solution is better, because:

  • We only re-render the query in the exceptional case.
  • We don’t finish rendering the query to calculate the actual count, but abort early for re-rendering. I.e. we don’t care if we have 2000, 5000, or 100000 bind values.

Conclusion

As with all exceptional techniques, remember to use them in the right moment. If in doubt, think again.
 

Related Whitepaper:

Bulletproof Java Code: A Practical Strategy for Developing Functional, Reliable, and Secure Java Code

Use Java? If you do, you know that Java software can be used to drive application logic of Web services or Web applications. Perhaps you use it for desktop applications? Or, embedded devices? Whatever your use of Java code, functional errors are the enemy!

To combat this enemy, your team might already perform functional testing. Even so, you're taking significant risks if you have not yet implemented a comprehensive team-wide quality management strategy. Such a strategy alleviates reliability, security, and performance problems to ensure that your code is free of functionality errors.Read this article to learn about this simple four-step strategy that is proven to make Java code more reliable, more secure, and easier to maintain.

Get it Now!  

Leave a Reply


one + 4 =



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.

Sign up for our Newsletter

20,709 insiders are already enjoying weekly updates and complimentary whitepapers! Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

As an extra bonus, by joining you will get our brand new e-books, published by Java Code Geeks and their JCG partners for your reading pleasure! Enter your info and stay on top of things,

  • Fresh trends
  • Cases and examples
  • Research and insights
  • Two complimentary e-books