mscharhag, Programming and Stuff;

A blog about programming and software development topics, mostly focused on Java technologies including Java EE, Spring and Grails.

  • Wednesday, 2 September, 2020

    REST: Dealing with Pagination

    In a previous post we learned how to retrieve resource collections. When those collections become larger, it is often useful to provide a way for clients to retrieve partial collections.

    Assume we provide an REST API for painting data. Our database might contain thousands of paintings. However, a web interface showing these paintings to users might only be able to show ten paintings at the same time. To view the next paintings the user needs to navigate to the next page which shows the following ten paintings. This process of dividing the content into smaller consumable sections (pages) is called Pagination.

    Pagination can be an essential part of your API if you are dealing with large collections.

    In the following sections we will look at different types of pagination

    Using page and size parameters

    The page parameter tells which page should be returned while size indicates how many elements a page should contain.

    For example, this might return the first page, containing 10 painting resources.

    GET /paintings?page=1&size=10

    To get the next page we simply increase the page parameter by one.

    Unfortunately it is not always clear if pages start counting with 0 or 1, so make sure to document this properly.

    (In my opinion 1 should be preferred because this represents the natural page counting)

    A minor issue with this approach might be that the client cannot change the size parameter for a specific page.

    For example, after getting the first 10 items of a collection by issuing

    GET /paintings?page=1&size=10

    we cannot get the second page with a size of 15 by requesting:

    GET /paintings?page=2&size=15

    This will return the items 15-30 of the collection. So, we missed 5 items (10-14).

    Using offset and limit parameters

    Another, but very similar approach is the use of offset and limit parameters. offset tells the server the number of items that should be skipped, while limit indicates the number of items to be returned.

    For example, this might return the first 10 painting resources:

    GET /paintings?offset=0&limit=10

    An offset parameter of 0 means that no elements should be skipped.

    We can get the following 10 resources by skipping the first 10 resources (= setting the offset to 10):

    GET /paintings?offset=10&limit=10

    This approach is a bit more flexible because offset and limit do not effect each other. So we can increase the limit for a specific page. We just need to make sure to adjust the offset parameter for the next page request accordingly.

    For example, this can be useful if a client displays data using a infinite scrollable list. If the user scrolls faster the client might request a larger chunk of resources with the next request.

    The downsides?

    Both previous solutions can work fine. They are often very easy to implement. However, both share two downsides.

    Depending on the underlying database and data structure you might run into performance problems for large offsets / page numbers. This is often an issue for relational databases (see this Stackoverflow questions for MySQL or this one for Postgres).

    Another problem is resource skipping caused by delete operations. Assume we request the first page by issuing:

    GET /paintings?page=1&size=10

    After we retrieved the response, someone deletes a resource that is located on the first page. Now we request the second page with:

    GET /paintings?page=2&size=10

    We now skipped one resource. Due to the deletion of a resource on the first page, all other resources in the collection move one position forward. The first resource of page two has moved to page one. 

    Seek Pagination

    An approach to solve those downsides is called Seek Pagination. Here, we use resource identifiers to indicate the collection offset.

    For example, this might return the first five resources:

    GET /paintings?limit=5

    Response:

    [
        { "id" : 2, ... },
        { "id" : 3, ... },
        { "id" : 5, ... },
        { "id" : 8, ... },
        { "id" : 9, ... }
    ]

    To get the next five resources, we pass the id of the last resource we received:

    GET /paintings?last_id=9&limit=5

    Response:

    [
        { "id" : 10, ... },
        { "id" : 11, ... },
        { "id" : 13, ... },
        { "id" : 14, ... },
        { "id" : 17, ... }
    ]

    This way we can make sure we do not accidentally skip a resource.

    For a relational database this is now much simpler. It is very likely that we just have to compare the primary key to the last_id parameter. The resulting query probably looks similar to this:

    select * from painting where id > last_id order by id limit 5;

    Response format

    When using JSON, partial results should be returned as JSON object (instead of an JSON array). Beside the collection items the total number of items should be included.

    Example response:

    {
        "total": 4321,
        "items": [
            {
                "id": 1,
                "name": "Mona Lisa",
                "artist": "Leonardo da Vinci"
            }, {
                "id": 2
                "name": "The Starry Night",
                "artist": "Vincent van Gogh"
            }
        ]
    }
    

    When using page and size parameters it is also a good idea to return the total number of available pages.

    Hypermedia controls

    If you are using Hypermedia controls in your API you should also add links for first, last, next and previous pages. This helps decoupling the client from your pagination logic.

    For example:

    GET /paintings?offset=0&limit=10
    {
        "total": 4317,
        "items": [
            {
                "id": 1,
                "name": "Mona Lisa",
                "artist": "Leonardo da Vinci"
            }, {
                "id": 2
                "name": "The Starry Night",
                "artist": "Vincent van Gogh"
            },
            ...
        ],
        "links": [
            { "rel": "self", "href": "/paintings?offset=0&limit=10" },
            { "rel": "next", "href": "/paintings?offset=10&limit=10" },
            { "rel": "last", "href": "/paintings?offset=4310&limit=10" },
            { "rel": "by-offset", "href": "/paintings?offset={offset}&limit=10" }
        ]
    }

    Note that we requested the first page. Therefore the first and previous links are missing. The by-offset link uses an URI-Template, so the client choose an arbitrary offset.

     

  • Thursday, 27 August, 2020

    REST: Retrieving resources

    Retrieving resources is probably the simplest REST API operation. It is implemented by sending a GET request to an appropriate resource URI. Note that GET is a safe HTTP method, so a GET request is not allowed to change resource state. The response format is determined by Content-Negotiation.

    Retrieving collection resources

    Collections are retrieved by sending a GET request to a resource collection.

    For example, a GET request to /paintings might return a collection of painting resources:

    Request:

    GET /paintings
    Accept: application/json
    

    Response:

    HTTP/1.1 200 (Ok)
    Content-Type: application/json
    
    [
        {
            "id": 1,
            "name": "Mona Lisa"
        }, {
            "id": 2
            "name": "The Starry Night"
        }
    ]
    

    The server indicates a successful response using the HTTP 200 status code (see: Common HTTP status codes).

    Note that it can be a good idea to use a JSON object instead of an array as root element. This allows additional collection information and Hypermedia links besides actual collection items.

    Example response:

    HTTP/1.1 200 (Ok)
    Content-Type: application/json
    
    {
        "total": 2,
        "lastUpdated": "2020-01-15T10:30:00",
        "items": [
            {
                "id": 1,
                "name": "Mona Lisa"
            }, {
                "id": 2
                "name": "The Starry Night"
            }
        ],
        "_links": [
            { "rel": "self", "href": "/paintings" }
        ]
    }
    

    If the collection is empty the server should respond with HTTP 200 and an empty collection (instead of returning an error).

    For example:

    HTTP/1.1 200 (Ok)
    Content-Type: application/json
    
    {
        "total": 0,
        "lastUpdated": "2020-01-15T10:30:00",
        "items": [],
        "_links": [
            { "rel": "self", "href": "/paintings" }
        ]
    }
    

    Resource collections are often top level resources without an id (like /products or /paintings) but can also be sub-resources. For example, /artists/42/paintings might represent the collection of painting resources for the artist with id 42.

    Retrieving single resources

    Single resources retrieved in the same way as collections. If the resource is part of a collection it is typically identified by the collection URI plus the resource id.

    For example, a GET request to /paintings/1 might return the painting with id 1:

    Request:

    GET /paintings/1
    Accept: application/json
    

    Response:

    HTTP/1.1 200 (Ok)
    Content-Type: application/json
    
    {
        "id": 1,
        "name": "Mona Lisa",
        "artist": "Leonardo da Vinci"
    }
    

    If no resource for the given id is available, HTTP 404 (Not found) should be returned.

     

  • Monday, 24 August, 2020

    OCR in Java with Tess4J

    Optical character recognition (OCR) is the conversion of images containing text to machine-encoded text. A popular tool for this is the open source project Tesseract. Tesseract can be used as standalone application from the command line. Alternatively it can be integrated into applications using its C++ API. For other programming languages various wrapper APIs are available. In this post we will use the Java Wrapper Tess4J.

    Getting started

    We start with adding the Tess4J maven dependency to our project:

    <dependency>
        <groupId>net.sourceforge.tess4j</groupId>
        <artifactId>tess4j</artifactId>
        <version>4.5.2</version>
    </dependency>

    Next we need to make sure the native libraries required by Tess4j are accessible from our application. Tess4J jar files ship with native libraries included. However, they need to be extracted before they can be loaded. We can do this programmatically using a Tess4J utility method:

    File tmpFolder = LoadLibs.extractTessResources("win32-x86-64");
    System.setProperty("java.library.path", tmpFolder.getPath());

    With LoadLibs.extractTessResources(..) we can extract resources from the jar file to a local temp directory. Note that the argument (here win32-x86-64) depends on the system you are using. You can see available options by looking into the Tess4J jar file. We can instruct Java to load native libraries from the temp directory by setting the Java system property java.library.path.

    Other options to provide the libraries might be installing Tesseract on your system. If you do not want to change the java.library.path property you can also manually load the libraries using System.load(..).

    Next we need to provide language dependent data files to Tesseract. These data files contain trained models for Tesseracts LSTM OCR engine and can be downloaded from GitHub. For example, for detecting german text we have to download deu.traineddata (deu is the ISO 3166-1-alpha-3 country code for Germany). We place one or more downloaded data files in the resources/data directory. 

    Detecting Text

    Now we are ready to use Tesseract within our Java application. The following snippet shows a minimal example:

    Tesseract tesseract = new Tesseract();
    tesseract.setLanguage("deu");
    tesseract.setOcrEngineMode(1);
    
    Path dataDirectory = Paths.get(ClassLoader.getSystemResource("data").toURI());
    tesseract.setDatapath(dataDirectory.toString());
    
    BufferedImage image = ImageIO.read(Main.class.getResourceAsStream("/ocrexample.jpg"));
    String result = tesseract.doOCR(image);
    System.out.println(result);

    First we create a new Tesseract instance. We set the language we want to recognize (here: german). With setOcrEngineMode(1) we tell Tesseract to use the LSTM OCR engine.

    Next we set the data directory with setDatapath(..) to the directory containing our downloaded LSTM models (here: resources/data).

    Finally we load an example image from the classpath and use the doOCR(..) method to perform character recognition. As a result we get a String containing detected characters.

    For example, feeding Tesseract with this photo from the German wikipedia OCR article might produce the following text output.

    ocr-example

    Text output:

    Grundsätzliches [Quelltext bearbeiten]
    Texterkennung ist deshalb notwendig, weil optische Eingabegeräte (Scanner oder Digitalkameras, aber
    auch Faxempfänger) als Ergebnis ausschließlich Rastergrafiken liefern können. d. h. in Zeiten und Spaten
    angeordnete Punkte unterschiedlicher Färbung (Pixel). Texterkennung bezeichnet dabei die Aufgabe, die so
    dargestellten Buchstaben als solche zu erkennen, dh. zu identifizieren und ihnen den Zahlenwert
    zuzuordnen, der ihnen nach üblicher Textcodierung zukommt (ASCII, Unicode). Automatische Texterkennung
    und OCR werden im deutschen Sprachraum oft als Synonym verwendet In technischer Hinsicht bezieht sich
    OCR jedoch nur auf den Teilbereich der Muster vergleiche von separierten Bildteilen als Kandidaten zur
    ( Erkennung von Einzelzeichen. Diesem OCR—Prozess geht eine globale Strukturerkennung voraus, in der
    zuerst Textblöcke von graphischen Elementen unterschieden, die Zeilenstrukturen erkannt und schließlich
    | Einzeizeichen separiert werden. Bei der Entscheidung, welches Zeichen vorliegt, kann über weitere
    \ . Algorithmen ein sprachlicher Kontext berücksichtigt werden
    

    Summary

    Tesseract is a popular open source project for OCR. With Tess4J we can access the Tesseract API in Java. A little bit of set up is required for loading native libraries and downloading Tesseracts LSTM data. After that it is quite easy to perform OCR in Java. If you are not happy with the recognized text it is a good idea to have a look at the Improving the quality of the output section of the Tesseract documentation.

    You can find the source code for the shown example on GitHub.

  • Tuesday, 18 August, 2020

    Introduction to Hypermedia REST APIs

    Introduction

    When browsing the web we typically navigate from one site to another by following Hyperlinks. Those links make the web for humans discoverable.

    Hypermedia APIs provide the same discoverability for services. According to Roy Fielding Hypermedia is an essential part of a REST API and the Richardson REST Maturity Model describes Hypermedia as the final step to the glory of REST. So, Hypermedia seems to be quite an important thing for REST. However, in my experience, Hypermedia is used very rarely. This is a bit sad because Hypermedia provides some nice features to APIs.

    Resource representations with links

    Let's start with a simple example without Hypermedia. Assume we want to create a resource that provides information about an order. It might look like this:

    GET /orders/123
    
    {
        "buyer_id": 456,
        "order_date": "2020-15-08T09:30:00",
        "total_price": 4.99,
        "payment_date": null,
        "status": "open",
        "items": [
            {
                "product_id" : 789,
                "quantity": 1,
                "price": 4.99
            }
        ]
    }
    

    Note that the fields buyer_id and product_id are references to other resources. If the client wants to get more information about the buyer, it has to construct a new request URI like this:

    String buyerUrl = "/customer/" + order.getBuyerId();

    Here the client has to know the exact URI format of related resources. This is similar to surfing the web without using hyperlinks. Instead of clicking on links we have to manually update the browser request line for every sub page we want to visit.

    To add Hypermedia support to our order representation, we have to replace IDs with links to related resources.

    For example:

    {
        "buyer_url": "/customers/456",
        "order_date": "2020-15-08T09:30:00",
        "total_price": 4.99,
        "payment_date": null,
        "status": "open",
        "items": [
            {
                "product_url" : "/products/789",
                "quantity": 5,
                "price": 4.99
            }
        ]
    }
    

    We now created links between related resources. A client does no longer have to care about IDs and URI construction. To get buyer information the client just has to send a GET request to the value of buyer_url.

    Hypermedia response formats typically group links together in a separate JSON object. It is also a good idea to use a JSON object to represent a link. This gives us the option to add more information to links later.

    If we apply this to our order representation it might look like this:

    {
        "order_date": "2020-15-08T09:30:00",
        "total_price": 4.99,
        "payment_date": null,
        "status": "open",
        "items": [
            {
                "quantity": 5,
                "price": 4.99,
                "links" : [
                    { "rel": "product", "href": "/products/789" }
                ]
            }
        ],
        "links" : [
            { "rel": "buyer", "href": "/customers/456" }
        ]
    }

    With the rel field we describe the type of the resource relation while href contains the actual link (more on this later).

    State Transitions (HATEOAS)

    So far we only used links to indicate relations to other resources. Links can also be used to indicate possible actions on a resource. For example, orders can be paid and cancelled. We can use links to point to these operations:

    {
        "order_date": "2020-15-08T09:30:00",
        "total_price": 4.99,
        "status": "open",
        "payment_date": null,
        "items": [ ... ],
        "links" : [
            { "rel": "buyer", "href": "/customers/456" },
            { "rel": "payment", "href": "/orders/123/payment" },
            { "rel": "cancellation", "href": "/orders/123/cancellation" }
        ]
    }

    In order to cancel an order we can now simply send a PUT request to the cancellation link. After cancelling the order, the resource representation might look like this:

    {
        "order_date": "2020-15-08T09:30:00",
        "total_price": 4.99,
        "status": "cancelled",
        "payment_date": null,
        "items": [ ... ],
        "links" : [
            { "rel": "buyer", "href": "/customers/456" },
        ]
    }

    Note that the order status has changed and the links for cancellation and payment are gone. Of course a cancelled order cannot be cancelled again and paying for a cancelled order makes no sense. So links do not just point to actions, they also tell us which actions are possible in the current resource status.

    This is called Hypermedia as the Engine of Application State (HATEOAS). HATEOAS can transform a REST API into a state machine over HTTP.

    More on Links

    We used quite a few links so far. So, it's a good point to look into a few details.

    The link attributes rel and href come from the attributes of the <a> tag that is used in HTML to represent links. A common set of link relations (like first, next, previous, etc.) has been standardized by IANA. You can find those relations on the IANA website. It is a good idea to take a look at this list before you come up with your own new rel type.

    It is also a good practice to include a link to the current resource, named self. For example:

    GET /orders/123
    
    {
        ...
        "links" : [
            { "rel": "self", "href": "/orders/123" },
            ...
        ]
    }

    Links might not always point to exact resources. It is also possible to create links that contain placeholders or optional parameters. For example, the order list might contain a search-by-status link that contains a status request parameter:

    GET /orders
    
    {
        ...
        "links" : [
            { "rel": "self", "href": "/orders" },
            { "rel": "search-by-status", "href": "/orders{?status}" },
            ...
        ]
    }

    Clients can use that link to filter the order list by a specific order status. For example, this might be a valid request:

    GET /orders?status=open

    These templates are called URI Templates (defined in RFC 6570). The RFC is a good source for more information.

    Links are also an important part of your API documentation. Instead of documenting exact resource URIs you should document possible link relations for your resources. The client needs to know what a specific links does and how it should be used (HTTP method, request body if required, etc.)

    The API entry point

    If clients do not know any resource URIs they need some entry point for an initial request. This initial entry point then provides links to accessible resources. An API entry point for our example API might look like this:

    GET /
    
    {
        "version": "1.2.3",
        "description": "Example API to manage orders",
        "links": [
            { "rel": "orders", "href": "/orders" },
            { "rel": "customers", "href": "/customers"},
            { "rel": "customer-by-id", "href": "/customer/{id}"},
            { "rel": "customer-by-email", "href": "/customer{?email}"},
            ...
        ]
    }

    With URI templates we can make sure clients do not need to browse through large collections in order to find a needed resource.

    Hypermedia response formats

    So far we just added links elements to our JSON representation. However, it can be a good idea to look at some common Hypermedia response formats before building a Hypermedia REST API. Unfortunately there is no single standard format. Instead, we can choose from a lot of different formats.

    Here are some examples:

    I would recommend looking at HAL first. HAL is quite simple and one of the formats that is widely supported by libraries. Besides standard REST clients you can use tools like HAL explorer to interact with APIs that use HAL.

    Why is this useful and what are the downsides?

    Introducing Hypermedia to REST APIs comes with a lot of benefits. It reduces coupling between the server and clients. Servers are able to refactor and evolve their URI structure without breaking clients. Clients no longer need to construct request URIs.

    It also reduces the logic required on the client. Let's recap the previous example with the order that can be cancelled or paid. However, this time without links:

    {
        "order_date": "2020-15-08T09:30:00",
        "total_price": 4.99,
        "status": "open",
        "payment_date": null,
        "items": [ ... ],
    }

    How does the client decide if it is possible to cancel or pay this order? Maybe an order can be cancelled as long as it is in open state? And it is possible to pay an order as long as it is in open state and payment_date is null?

    This logic is already present on the server and can be communicated with HATEOAS. So instead of duplicating logic the client has just to check if a specific link is present. For example: If the cancellation link is present, it is possible to cancel the order and therefore the Cancel order button should be shown in the user interface.

    The same approach works great for communicating allowed operations. The server already contains the logic to decide what a user is allowed to do based on his permissions/roles. So, if a user has no permission to cancel an order, don't add a cancellation link.

    Those points are all great, but what are the downsides?

    Adding links for resource relations and state transitions can be a significant effort on the server side. You have to construct links, list possible state transitions and check if the client has the permissions use them. This effort is only useful if clients actually make use of the Hypermedia elements provided by the API and do not use hardcoded URIs.

    Using Hypermedia can also significantly increase the response size.

    Summary

    Hypermedia REST APIs use links to point to related resources and to possible resource state transitions. This makes REST APIs discoverable and reduces coupling between clients and the server. Clients can interact with links provided by the server instead of constructing URIs on their own. It also reduces logic duplication on client side.

    However, implementing Hypermedia can be a significant effort on the server side.

    Many different Hypermedia response formats are available, a simple and popular one is HAL.

  • Tuesday, 11 August, 2020

    Extending JUnit 5 (Jupiter)

    In this post we will learn how JUnit 5 extensions work and how we can create custom extensions.

    A look into the past: Runners and Rules

    With JUnit 4 we have the option to run tests with a custom JUnit runner (indicated by the @RunWith annotation). This allows us to modify the way tests are executed with JUnit. However, JUnit runners are not that easy to implement. They also suffer on the major limitation that only one runner can be used in a test.

    With JUnit 4.7 Rules were introduced. Rules use a different concept to customize tests. It is also possible to use multiple rules within a test. So from this point JUnit 4 had two different ways (with different up and downsides) to customize test behavior.

    JUnit 5 introduces extensions

    This whole customization mechanism has changed with JUnit 5 which introduced extensions. Extensions can be added to tests in various ways. The most common way is the @ExtendWith annotation that can be used on test classes or on single test methods. For example:

    @ExtendWith(MyFirstExtension.class)
    public class DemoTest {
    
        @Test
        public void test() {
            // uses MyFirstExtension
        }
    
        @Test
        @ExtendWith(MySecondExtension.class)
        public void test2() {
            // uses MyFirstExtension and MySecondExtension
        }
    }

    Extensions added to the test class will be used for all test methods within the class.

    Multiple extensions can be registered by passing an array of extensions to the @ExtendWith annotation:

    @ExtendWith({ MyFirstExtension.class, MySecondExtension.class })
    public class DemoTest {
        ...
    }

    @ExtendWith is also a repeatable annotation, so it can be added multiple times:

    @ExtendWith(MyFirstExtension.class)
    @ExtendWith(MySecondExtension.class)
    public class DemoTest {
        ...
    }

    Note that @ExtendWith can be composed to other annotations. For example, we can come up with our own annotation that is annotated with @ExtendWith:

    @Retention(RetentionPolicy.RUNTIME)
    @ExtendWith(MockWebServerExtension.class)
    @ExtendWith(MockDatabaseExtension.class)
    @Target(ElementType.TYPE)
    public @interface IntegrationTest {}

    We can now annotate our test with @IntegrationTest and JUnit 5 will run the tests using the two extensions defined in @IntegrationTest:

    @IntegrationTest
    public class DemoTest {
        ...
    }

    While the @ExtendWith annotation is easy to use and works fine in most situations it has a drawback. Sometimes test code needs to interact with an extension or the extension might need some sort of configuration or setup code. This cannot be done if the extension is defined with @ExtendWith.

    In these situations we can create the extension manually, assign it to a field and add the @RegisterExtension annotation. For example lets look at a fictional extension that manages temporary files in a test:

    public class DemoTest {
    
        @RegisterExtension
        static TempFileExtension tempFiles = TempFileExtension.builder()
                .setDirectory("/tmp")
                .deleteAfterTestExecution(true)
                .build();
    
        @Test
        public void test() {
            File f = tempFiles.newTempFile("foobar.tmp");
            ...
        }
    }

    Using a @RegisterExtension on a field gives us the option to configure the extension and to interact with the extension in test methods.

    Creating custom extensions

    Creating a custom extension for JUnit 5 is quite easy. We just have to create a class that implements one or more of JUnits extension interfaces.

    Assume we want to create a simple extension that measures how long a test runs. For this we create a new class that implements the interface InvocationInterceptor.

    public class TestDurationReportExtension implements InvocationInterceptor {
    
        @Override
        public void interceptTestMethod(Invocation<Void> invocation,
                ReflectiveInvocationContext<Method> invocationContext,
                ExtensionContext extensionContext) throws Throwable {
    
            long beforeTest = System.currentTimeMillis();
            try {
                invocation.proceed();
            } finally {
                long afterTest = System.currentTimeMillis();
                long duration = afterTest - beforeTest;
                
                String testClassName = invocationContext.getTargetClass().getSimpleName();
                String testMethodName = invocationContext.getExecutable().getName();
                System.out.println(String.format("%s.%s: %dms", testClassName, testMethodName, duration));
            }
        }
    }

    InvocationInterceptor has various methods with default implementations. We override the implementation of interceptTestMethod(..). This method lets us run code before and after a test method is executed. With the proceed() method of the Invocation method parameter we can proceed with the actual test execution.

    We simply subtract the system time before the test from the system time after the test execution to get the duration. After that, we use the InvocationContext parameter to obtain the names of the test class and test method. With this information we create a formatted output message.

    Now we can extend tests with our TestDurationReportExtension by using the @ExtendWith annotation:

    @ExtendWith(TestDurationReportExtension.class)
    public class DemoTest { .. }

    When running tests, we will now see our extension output for every test method.

    The output for a test with two methods might look like this:

    DemoTest.slowTest: 64ms
    DemoTest.fastTest: 6ms

    Extension interfaces

    InvocationInterceptor is just one various extension interfaces. In this section, we will briefly look over these different interfaces and for what they can be used.

    Conditional test execution

    By implementing the interface ExecutionCondition an extension can decide if a test should be executed. This lets the extension decide if certain tests should be skipped. A simple example is the standard extension DisabledCondition that skips tests annotated with @Disabled.

    Test instance factories

    By default JUnit 5 will instantiate test classes by invoking the available constructor (if multiple test constructors are available an exception will be thrown). Possible constructor arguments are resolved using ParameterResolver extensions (see below).

    This default behavior can be customized using the TestInstanceFactory interface. An Extension that implements TestInstanceFactory is used as factory for creating test class instances. This is can be used to create Tests via static factory methods or to inject additional parameters into the test constructor.

    Processing test instances

    After a test instance has been created, the TestInstancePostProcessor interface can be used to post process test instances. A common extension use case for this is the injection of dependencies into fields of the test instance. Similarly the TestInstancePreDestroyCallback can be used to run custom cleanup logic, when a test has finished and the instance is no longer needed.

    Test parameter resolution

    Test class constructors or methods annotated with @Test, @BeforeEach, @BeforeAll etc. can contain parameters. These parameters are resolved at runtime by JUnit using ParameterResolvers. Extensions can implement ParameterResolver if they want to support additional parameters.

    Test Lifecycle callbacks and interceptions

    JUnit 5 provides a couple of test lifecycle callback interfaces that can be implemented by extensions:

    • BeforeAllCallback, runs before @BeforeAll methods in the test class
    • BeforeEachCallback, runs before @BeforeEach methods in the test class
    • BeforeTestExecutionCallback, runs before the test method
    • AfterTestExecutionCallback, runs after the test method
    • AfterEachCallback, runs after @AfterEach methods in the test class
    • AfterAllCallback, runs after @AfterAll methods in the test class

    Those interfaces provide a simple callback to do something at a certain time in the test lifecycle.

    Additionally there is the InvocationInterceptor interface we already used in the extension example above. InvocationInterceptor has similar methods as the callback interfaces. However, InvocationInterceptor gives us an Invocation parameter that allows us to manually continue the lifecycle by calling the proceed() method. This is useful if we want to wrap code around the invocation, like a try/catch block.

    Summary

    Writing extensions for JUnit 5 is quite easy. We just have to create a class that implements one or more of JUnits extension interfaces. Extensions can be added to test classes (or methods) using the @ExtendWith and @RegisterExtension annotations. You can find the source code for the example extension on GitHub. Also make sure to checkout the excellent JUnit 5 user guide.