April 22, 2020

1631 words 8 mins read

Fast, exception free* Spring applications

Fast, exception free* Spring applications

There is ample evidence that throwing an exception in JVM incurs a penalty. An exception, when thrown, results in capturing filling in the stack trace. This operation will, in most cases result in longer execution time compared to the method that returns a value.

JVM developers have been diligently avoiding exceptions for flow control. We have been trained over the course last few decades that when possible, we should return value rather than throw an exception.

And yet, every time we start building a new Spring Framework project, we do the opposite.

Spring Error Handling

To prevent committing request in error, Spring Transaction Manager in going to abort JPA transaction on RuntimeException.

When an incorrect or incomplete request reaches an HTTP endpoint, the Java Validation is going to issue ConstraintViolationException preventing triggering business logic.

The RabbitMQ event handler is going to either re-queue or drop an event, depending on the exception thrown by the message handling logic.

1
2
3
4
5
if(dropOnFailure) {
  throw new AmqpRejectAndDontRequeueException("Forget it!");
} else {
  throw new AmqpException("Hit me again!");
}

These and many other examples illustrate how Spring and similar frameworks, leverage exceptions as a quasi flow control or error handling mechanism. This mechanism can slowly eat up your system’s performance or obfuscate part of the operations.

Exceptions are not evil

The exceptions are not the source of all evil. Carefully placed and thrown exception can quickly abort unrecoverable flow across multiple layers. Security exception can pierce multiple levels of logic assuring that nothing gets executed when access guards are not satisfied.

There are valid and legitimate use cases for exceptions.

But it is also true that they are easily overused.

Going Exceptionless (almost)

Writing the exceptionless Java Spring application is possible and worthwhile doing. It has the potential of yielding clearer, more explicit codebase.

Authors of The Zen of Python, put it succinctly:

Explicit is better than implicit.

Fewer exception thrown will also improve performance. While occasional exceptions might have little impact on the bottom line, imagine the system battered by the ill-configured or malicious client. A flood of failures and thus exceptions in only going to add insult to the injury.

Reach for Binding Result explicitly

Spring Framework is an incredible tool. It makes 80% of development tasks easy and handled by, what appears to be, pure magic. Creating new POST endpoint for REST-like service is as easy defining controller with a body of HTTP request mapped to Java/Kotlin POJO.

1
2
3
4
5
6
7
8
9
@RestContoller
class FooController {

  @PostMapping
  fun createFoo(@Valid @RequestBody request : CreateFooRequest) : ResponseEntity<Foo> {
	// ...
  }

}

Inside of createFoo(..) we can be sure that once called, we have access to an instance of CreateFooRequest which satisfies Java Validation constraints we defined. But what if one or more constraints fails?

The Spring Framework is going to notify the client about Bad Request being issued. Should you build an API endpoint aimed for a selected JavaScript framework/library to consume, you are going to be faced with the necessity to reformat error response.

At this point, you can add controller specific @ExceptionHandler or write generic, application-wide error response generator using @ControllerAdvice.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@ControllerAdvice
class ErrorHandlingControllerAdvice {

  @ExceptionHandler(ConstraintViolationException::class)
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ResponseBody
  fun onConstraintValidationException(errors: ConstraintViolationException): RequestErrors {
  	 // ...
  }

}

At your disposal is the whole catalogue of exceptions for all sort of edge cases.

At the same time, Spring offers more explicit handling of binding errors. Each controller method can be provided with a plethora of additional parameters automagically provided by Spring. One of which is BindingResult. Having access to binding result, we can regain control and explicitly define what controller does and when.

First, let’s define a wee bit of common helpers that we can subsequently leverage to avoid writing lots of verbose error handling code. We start with Either. You can get it for free with vavr.io or in Scala. You can easily define rudimentary (not monadic) implementation in Kotlin.

1
2
3
4
sealed class Either<LEFT, RIGHT> {
    class Left<LEFT, RIGHT>(val left: LEFT) : Either<LEFT, RIGHT>()
    class Right<LEFT, RIGHT>(val right: RIGHT) : Either<LEFT, RIGHT>()
}

With Either in place, following extension function on BindingResult will make our controller code DRYier.

1
2
3
4
5
6
7
8
9
typealias Result<T> = Either<ResponseEntity<Any>, T>

fun <T> BindingResult.failOnError(request : T) : Result<T> {
  if(hasErrors()) {
    val responseEntity : ResponseEntity<Any> = ResponseEntity.unprocessableEntity().body(toFailure().toResponse())    
    return Either.Left(responseEntity)
  }
  return Either.Right(request)
}

And finally, in our controller, we capture Success and Failure branches as follows:

1
2
3
4
5
6
7
8
@PostMapping
fun createBillableService(@Valid @RequestBody request: CreateBillableRequestWrapper, bindingResult: BindingResult): Publisher<ResponseEntity<Any>> {
  val binding = bindingResult.failOnError(request)
  return when (binding) {
    is Either.Right -> handleRequest(binding.right.billable!!) // Success, process
    is Either.Left -> binding.left // Failure
  }
}

The final controller gives us full control over biding errors. Allows for explicit error handling and further allows the developer to control how to respond to failure. Whether with 422, 404 or different error code, one can define explicit status on ResponseEntity. No exceptions needed.

Return, don’t throw

Algebraic Data Types heavily leveraged in Functional Programming can be an incredibly useful tool in more traditional Java/Kotlin OOP applications. By expressing every possible state as a type, we can assure that all possible outcomes are handled. Reading an explicit but compact code relieves developer for cognitive burden induced by stacks of annotations, and implicit framework enforced behaviour.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
interface FindFooUseCase {

  fun findFoo(query: FooQuery) : FooResponse

  sealed class FooResponse {

    data class FooFoundResponse(foo :Foo) : FooResponse()    
    data class FooNotFoundResponse(id: FooId) : FooResponse() 
    data class DownstreamFailureResponse(message: String) : FooResponse() 
  }

}

With use case having an explicit return type, our controller can respond with status appropriate to response type:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
ControllerAdvice
class ErrorHandlingControllerAdvice(
  @Autowired val findFoo : FindFooUseCase) {

  @GetMapping
  fun findFoo(@PathVariable id : FooId) : ResponseEntity<Any> {
  	val query = ...
  	val response = findFoo(query)
  	return when(response) {
  		is FooFoundResponse -> handleSuccess(response.foo) // 200
  		is FooNotFoundResponse -> handleNotFound(response.id) // 404
  		is DownstreamFailureResponse -> handleFailrue(response.message) // 500 with reason
  	}
  }
}

Although slightly more verbose, it is still a very readable code. Moreover, we can map each domain response type, agnostic of the HTTP layer, to an appropriate response type.

Once again, we have maintained control over response, without employing exceptions.

Explicitly invalidate and rollback transaction

Using explicit types for error handling prevents Spring’s Transaction Manager from automatically rolling back transactions. Should the controller or FindFooUseCase be annotated with @Transactional, as we don’t throw RuntimeException, the transaction would not get rolled back, and we could end up with invalid state persisted in the database, far from ideal.

We have a few options to handle this problem. We could opt for handling transaction explicitly by getting ahold of TransactionManager instance. Assuming you are building more traditional CRUD application, you are likely focusing on one transaction per HTTP request. As such and overheard of manual transaction handling in a controller might be too high. Unnecessary complexity is spilling all over controller the code.

A different and depending on the use case, a simpler option is a combination of dedicated type and AspectJ.

We start by defining a marker interface:

1
interface NonRecoverableError 

and a annotation that we will use to instrument AspectJ with:

1
2
3
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class TransactionalWithErrorDetection

With these two building blocks, we can create a simple, yet powerful automatic and transparent transaction handler:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Aspect
@Configuration
class TransactionalWithErrorDetectionInterceptor(@Autowired val transactionManager: PlatformTransactionManager) {

  private val template: TransactionTemplate = TransactionTemplate(transactionManager)

  @Around("@within(foo.TransactionalWithErrorDetection) || @annotation(foo.TransactionalWithErrorDetection)")
  fun handleTransaction(pjp: ProceedingJoinPoint): Any? {
    return template.execute { tx ->
      var result: Any?
      try {
        result = pjp.proceed()
        if (result is NonRecoverableError) {
          tx.setRollbackOnly()
        }
      } catch (e: Throwable) {
        tx.setRollbackOnly()
        throw e
      }
      return@execute result
    }
  }
}

In the above snippet, we have defined a new aspect that will trigger around a method of a type or specific function. If the type returned by the method/function is a subtype of NonRecoverableError, the aspect will rollback the transaction. The actual return type will propagate further, allowing the caller to handle return type. Here is sample application:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
sealed class FooResponse {
  data class FooCreatedResponse(foo : Foo) : FooResponse()
  data class FooFailedResponse(message : String) : FooResponse(), NonRecoverableError
}


@TransactionalWithErrorDetectionInterceptor
@Service
class CreateFooService {

  /**
   * Even if the method returns `FooFailedResponse`, triggering TX rollback, 
   * the caller of `createFoo` will receive the return value. No flow interruption. 
   */
  fun createFoo(request : FooRequest) : FooResponse {  
  	// ..  
  }

}

Whenever createFoo is called, a transaction will be created. Should that method return FooFailedResponse transaction will be rolled-back. Otherwise, it will be committed.

Summary

Spring Framework offers a few different ways to implements HTTP endpoints. One of the commonly used approaches leverages Annotation-based Controllers which enable writing short and clean code. At the same time, this type of solution will relay heavily on exceptions. These, in turn, can consume resources but also obfuscate vital code paths.

In this post, we illustrated a solution to a few everyday use cases, such as binding errors and transaction handling without exceptions.

Type-centring and exception-less programming style with judicious application of Runtime Exception can yield code that is easy to read, decreases cognitive load, gives developer more control and at the same time does not necessarily mean more code.

Examples in this blog are leveraging Kotlin, but similar compact implementation can be achieved with Java 8+ and vavr.io.

Copyright © 2017–2020, Input Objects GmbH; all rights reserved.