MVC stands for Model-View-Controller, and JavaFX is fundamentally structured for it, along with similar patterns like MVP and MVVM. JavaFX’s support for events, properties, binding and FXML documents all help facilitate this. They’re there to allow you to to separate the business logic from the user interface.

Separate Views should be created for different user requirements

Every element of the JavaFX MVC pattern is defined by one or more Java objects. In fact, MVC in JavaFX can be implemented both with and without the use of FXML.

I personally prefer to use FXML to structure and create my View objects, because of its simplicity, the in-built links to Controller and CSS files and the convenience of the FXMLLoader. However, once loaded into memory, the View is still just a collection of Java objects.

With that in mind, in this article, I’ll go through how to implement the MVC pattern in both hybrid (Java/FXML) and Java-only systems.

JavaFX MVC is deployed entirely in Java, although some elements are loaded from other files

To apply MVC in JavaFX, we’ll need to apply three principles:

  1. View logic should be included in the Controller and defines how information is displayed and interacted with
  2. Business logic should be included in the Model and defines how data can be accessed, created, stored and changed
  3. Each View should have one simple and consistent narrative or purpose. In many complex applications there can even be multiple views within a window. In fact that often helpfully breaks down the responsibilities and enables the code to be reused elsewhere in the program.
The MVC pattern facilitates re-use of code across multiple windows or apps with minimal modification of code

The MVC Pattern overall should facilitate re-use of code across multiple windows or apps with no modification of the modular elements.

Without MVC, we tend to commit to tighter coupling between the business logic and the view logic. That means that if you need to make changes to anything from behaviour to data validation, you’re committing to significant refactoring.

What you’ll get from this article

In this article, you’ll get everything you need to design, implement and structure a JavaFX project using MVC structure.

Table of contents:

There are a lot of different ways to implement MVC in JavaFX. The implementation depends on the complexity of the user interface, preference, and environment. Correspondingly, this article comes with a disclaimer that this is my advice on how to create relatively simple JavaFX apps with a reusable, modular MVC pattern.

Roles and responsibilities of Model, View and Controller

The MVC pattern fundamentally separates the responsibilities for data, control and visualisation into separate objects. These objects should work together to process user input, store data, and visualise it to the screen.

The MVC design pattern in JavaFX powers event-driven UI design

By creating MVC architecture using the FXML/Java hybrid, we can separate view logic from business logic, and in the process we’ll generate reusable classes that we can use over and over again.

You keep mentioning them… but what are business logic and view logic?

That’s a fair question, and there can be some overlap (where the view logic implements some higher principle based on the business rules or logic), but here are some examples of each:

Business Logic

  • What format should a user’s street address take?
  • What happens if two users sign up, simultaneously taking the last spot
  • Should a user be able to update their email address, and how should it be validated?

View Logic

  • What happens when a User clicks the ‘OK’ button?
  • How many records should I display in my table at one time?
  • Which fields need to be complete before the OK button is activated?*

*Some view logic – like “which fields need to be complete before the OK button is activated” will be based on business logic (and business rules), such as “users must have both an email address and a postcode.

The MVC design pattern can seem confusing, because as soon as you mention it, people start talking about domain models, aggregate roots, and repository patterns.

There’s nothing wrong with a good aggregate root (as my mum always used to say 👀), but as a self-taught programmer, it always made more sense to me in simple terms. Here are the concepts of Model, View and Controller as they seem to me.

Model

The model should be a user-interface specific class that defines all of the data in the View, and in JavaFX, it should:

  1. Provide methods that the Controller can invoke to update the Model
  2. Provide properties that we will bind to controls and nodes in the View, enabling it to visualise data and changes to the user.
In JavaFX, the model in MVC is responsible for providing properties to the View and methods for the controller

It could be that you have a lot of smaller classes sit behind the Model and provide it with the data it needs, because Models can quickly become cumbersome. That additional modularisation of code will also pay off in the future if you later need to refactor the way your data layer works too.

View

The view is a visual representation of the data relevant to the user at that point in an application, and in JavaFX, it should

  1. Use JavaFX’s properties and binding with the Model so that changes to the model are reflected instantaneously to the user
  2. Invoke methods in the Controller that reflect the user’s interactions with the View.
The view should use the bindings provided by the model, and invoke methods in the controller

The View’s special role in JavaFX

In JavaFX, the FXML file used to generate the View also provides a critical role in MVC loading. The FXML file should contain a document reference to the Controller object. This facilitates loading of Controller and binding it with the View at initialization.

The model must contain links to the controller to facilitate loading and linking

Controller

The controller should mediate between the user’s actions and the effect on the data model and the view, and in JavaFX it should:

  1. Provide methods that the View can invoke based on the user’s interactions
  2. Invoke methods in the Model to update it based on the user’s interactions, or the Controller’s own background processes
The controller should use methods to update model, and provide methods for the view to invoke

JavaFX Project Structure with MVC

For a basic implementation of MVC in JavaFX, you need four files:

  1. FXML file to load the View,
  2. Java class to act as Controller, and a
  3. Java class to act as the Model
  4. A class to start your app, which extends the JavaFX Application class.

We’ll create the View and Controller first using an FXMLLoader, and then we’ll define the Model and connect it up.

The way files are linked in an MVC app with JavaFX
In this simple example, the View is the same as the Scene Graph, because we’ll only be loading one View

If you’re not sure where to put each of the files inside your App, take a look at the Project Structure dropdown below, else let’s get started loading up our App.

So, once we have our files in place, all we have to do is link them together. We’ll start with the main Application class, which creates our Controller and View using an FXMLLoader, and then move onto creating the other connections.

MVCExampleApp

The first link we’ll create is the link between the Controller and the View. This is created when we load the View using the FXMLLoader inside the MVCExampleApp class.

package com.edencoding;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class MVCExampleApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("/fxml/mainView.fxml"));
        primaryStage.setTitle("MVC Example App");
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

In very simple apps like this, the View constitutes everything in the Scene Graph, but for more complex applications, we can create a more complex Scene Graph with multiple views. The View is set into the window by invoking primaryStage.setScene(new Scene(root)).

The View and Controller are both created as the FXMLLoader parses the FXML document. Which links you create between the View and Controller are defined by information provided in the FXML document.

The FXMLLoader creates the constructor and the scene graph, but does not link the model

It does that in roughly this order:

  1. Creating Java objects for the View based on the FXML document it’s reading
  2. Creating the Constructor object based on the fx:controller reference in the root node, and
  3. Injecting references for the View into @FXML-annotated fields in the Controller.

Let’s take a look at the View and Controller in turn.

View

In an FXML-powered MVC set up, the nodes defined in an FXML document will structure the way the View is presented on screen. As it’s loaded, the FXML document defines which nodes are presented where, and how those objects are laid out on the screen.

A Simple JavaFX Scene Graph

When you’re creating your FXML document, there are also a few ways we can embed View-Controller connections so that the FXMLLoader creates them as it loads the document.

  1. The fx:controller tag on the root node identifies which Java object the FXMLLoader should create as the Controller.
  2. Wherever an fx:id tag is used, this will automatically connect the node it represents with a Java object in the controller
  3. Wherever an Event convenience method is mentioned (such as onAction), this will automatically connect the Event with a method in the Controller.

And here’s how they’ll look inside the FXML document:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1"
      prefHeight="250.0" prefWidth="300.0" 
      spacing="15.0" 
      stylesheets="@../css/styles.css"  
      fx:controller="com.edencoding.controllers.MainViewController">
    <Label alignment="CENTER" maxWidth="600" styleClass="title" text="Bank Account" />
    <GridPane>
        <columnConstraints>
            <ColumnConstraints hgrow="SOMETIMES"/>
            <ColumnConstraints hgrow="SOMETIMES"/>
        </columnConstraints>
        <rowConstraints>
            <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES"/>
            <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES"/>
            <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES"/>
        </rowConstraints>
        <Label styleClass="bold" text="Account holder:"/>
        <Label fx:id="accountHolder" GridPane.columnIndex="1"/>
        <Label styleClass="bold" text="Account Number:" GridPane.rowIndex="1"/>
        <Label fx:id="accountNumber" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
        <Label styleClass="bold" text="Balance:" GridPane.rowIndex="2"/>
        <Label fx:id="accountBalance" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
    </GridPane>
    <HBox alignment="CENTER" spacing="25.0">
        <Button onAction="#handleWithdrawal" text="Withdraw"/>
        <TextField fx:id="amountTextField" prefWidth="75.0" promptText="Number"/>
        <Button layoutX="10.0" layoutY="10.0" onAction="#handleDeposit" text="Deposit"/>
    </HBox>
    <padding>
        <Insets topRightBottomLeft="15" />
    </padding>
</VBox>

This is the View we’re creating with that code:

A simple JavaFX app to display bank account information

Controller

An important part of setting up the ViewController connections is defining fields ad methods within the Controller object that the FXMLLoader can connect with the View. Here are a few ways you can do that

  1. Create @FXML-annotated (or public) fields with names and types that match the fx:id tags in the FXML document
  2. Create @FXML-annotated (or public) methods that accept an Event as an argument and define the action taken when a View object is interacted with (the type of interaction is defined in the View using convenience methods like onAction and onMouseClicked)

Here’s how that looks in our code:

package com.edencoding.controllers;

import com.edencoding.models.FinancialAccount;
import javafx.event.Event;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;

public class MainViewController {

    //View nodes
    @FXML private Label accountHolder;
    @FXML private Label accountNumber;
    @FXML private Label accountBalance;
    @FXML private TextField amountTextField;

    @FXML private void handleDeposit(Event event) {
        account.deposit(getAmount());
        event.consume();
    }

    @FXML private void handleWithdrawal(Event event) {
        account.withdraw(getAmount());
        event.consume();
    }

    private double getAmount(){
        if (amountTextField.getText().equals("")) return 0;

        return Double.parseDouble(amountTextField.getText());
    }
}

The next step is to create our Model class and link it with the View and Controller.

Model

Once the FXMLLoader has created the View and connected it with the Controller, it fires off the Controller’s initialize() method and this is the perfect point to create your ControllerModel and ModelView connections.

In an example app, let’s say we want to display the bank account information for someone. The Model for this app will be incredibly simple. In addition to some getters and a constructor, we’ll define two convenience methods deposit(double amount) and withdraw(double amount) to either increase or decrease the account balance:

public class FinancialAccount {

    private final StringProperty accountHolder;
    private final IntegerProperty accountNumber;
    private final DoubleProperty accountBalance;

    //getters, setters, constructor

    public void deposit(double amount){
        accountBalance.set(accountBalance.get() + amount);
    }

    public void withdraw(double amount){
        accountBalance.set(accountBalance.get() - amount);
    }
}

Once you’ve created the Model class, you’ll want to be able to make the final connections from the Model to the View and Controller.

You can’t do this straight away (in the Controller’s constructor). You’ll need to do this in the initialize() method. If you want to take a look at why this is the case, check out the drop-down below.

Model-View and Model-Controller connections

The connections we’ll need for an app will be defined by the functionality we want. In this case, we want to ensure:

  1. The account holder, account number and balance fields should display the correct values for the account
  2. The Number text input field should only allow numeric inputs
  3. When a user presses the withdraw or deposit buttons, the number in the input field should be deducted, or added to the balance as appropriate.

The first and second connections we can create from the initialize() method of the Controller. The third we already accomplished above by using the button’s convenience onAction method in the FXML document and binding it to @FXML-annotated methods in the Controller.

To complete this MVC app, we need to feed the @FXML-annotated fields we defined in the Controller above with the Model.

Now we have our Model, we can add those links to the Controller. We’ll create a FinancialAccount object, which acts as our Model, and as a reminder I’ve included the @FXML-annotated fields we included before. Inside the initialize() method of the Controller, these @FXML-annotated fields will correctly link with the nodes of the View.

//Model
FinancialAccount account;

//View nodes
@FXML private Label accountHolder;
@FXML private Label accountNumber;
@FXML private Label accountBalance;
@FXML private TextField amountTextField;


public void initialize(){
    //get model
    account = new FinancialAccount("Maxwell Planck", 6626, 1000d);

    //link Model with View
    accountHolder.textProperty().bind(account.accountHolderProperty());
    accountBalance.textProperty().bind(account.accountBalanceProperty().asString());
    accountNumber.textProperty().bind(account.accountNumberProperty().asString());

    //link Controller to View - ensure only numeric input (integers) in text field
    amountTextField.setTextFormatter(new TextFormatter<>(change -> {
        if (change.getText().matches("\\d+") || change.getText().equals("")) {
            return change;
        } else {
            change.setText("");
            change.setRange(
                    change.getRangeStart(),
                    change.getRangeStart()
            );
            return change;
        }
    }));
}

There’s a little bit of extra code here that’s part of the Controller’s role in defining the behaviour of the View. The code used to set a TextFormatter on the ammountTextField ensures only numbers (integers here, actually) can be used as input.

If you want to find out more about how to control inputs in a TextField, check out this article here.

And, with that, we’ve created our MVC app! It’s also possible to create this App without FXML, for those who want to stick to Java-only development. It’s not my preferred way of doing things, but it’s definitely an option.

Or, you can jump down to the best practices for MVC.

Full code:

If you want the full code, you can get it all in my GitHub here.

How to use MVC in JavaFX without FXML

You can use MVC in JavaFX without FXML by creating the Java objects contained in the View using entirely Java code, and linking these with a Controller and Model, just as before.

Warning: you’ll notice in this example there is tight coupling between the View and the Controller. This is because we’re not injecting the fields into the Controller with an FXMLLoader. You can avoid this by programming to an interface, for example. However, as a general rule, if you create systems without FXML you tend to commit to tighter coupling unless you seriously invest in engineering a looser strategy.

How to create a View without an FXML document

To create the View, here we’ll manually construct the same simple application as before but entirely in Java.

A simple JavaFX app to display bank account information

Note we’ll need to expose the labels for the account details, the quantity text field and the buttons using getters so that we can use them in the Controller later.

The View

package com.edencoding.views;

import com.edencoding.MVCExampleApp;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.*;

public class MainView {

    //View Nodes
    Label accountHolderText = new Label("");
    Label accountNumberText = new Label("");
    Label accountBalanceText = new Label("");
    Button withdrawalButton = new Button("Withdraw");
    Button depositButton = new Button("Deposit");
    TextField amountTextField;

    //View
    Parent view;

    public MainView() {
        view = createView();
    }

    public Label getAccountHolderDetailsLabel() {
        return accountHolderText;
    }

    public Label getAccountNumberDetailsLabel() {
        return accountNumberText;
    }

    public Label getAccountBalanceDetailsLabel() {
        return accountBalanceText;
    }

    public Button getWithdrawalButton() {
        return withdrawalButton;
    }

    public Button getDepositButton() {
        return depositButton;
    }

    public TextField getAmountTextField() {
        return amountTextField;
    }

    private VBox createView(){
        VBox vBox = new VBox(15);
        vBox.setPrefWidth(300);
        vBox.setPrefHeight(250);
        vBox.setPadding(new Insets(15));
        vBox.getStylesheets().addAll(MVCExampleApp.class.getResource("/css/styles.css").toExternalForm());

        vBox.getChildren().add(createTitle());
        vBox.getChildren().add(createAccountDetails());
        vBox.getChildren().add(createDepositWithdrawalButtons());
        return vBox;
    }

    private Node createTitle(){
        Label titleLabel = new Label("Bank Account");
        titleLabel.getStyleClass().add("title");
        titleLabel.setMaxWidth(600);
        return titleLabel;
    }

    private Node createAccountDetails() {
        GridPane gridPane = new GridPane();

        Label accountHolderLabel = new Label("Account holder:");
        Label accountNumberLabel = new Label("Account holder:");
        Label accountBalanceLabel = new Label("Account holder:");
        accountHolderLabel.getStyleClass().add("bold");
        accountNumberLabel.getStyleClass().add("bold");
        accountBalanceLabel.getStyleClass().add("bold");

        GridPane.setRowIndex(accountNumberLabel, 1);
        GridPane.setRowIndex(accountBalanceLabel, 2);
        GridPane.setConstraints(accountHolderText, 1, 0);
        GridPane.setConstraints(accountNumberText, 1, 1);
        GridPane.setConstraints(accountBalanceText, 1, 2);

        gridPane.getChildren().addAll(
                accountHolderLabel, accountNumberLabel, accountBalanceLabel,
                accountHolderText, accountNumberText, accountBalanceText
        );

        gridPane.getRowConstraints().addAll(
                new RowConstraints(30, 100, 500, Priority.SOMETIMES, VPos.BASELINE, true),
                new RowConstraints(30, 100, 500, Priority.SOMETIMES, VPos.BASELINE, true),
                new RowConstraints(30, 100, 500, Priority.SOMETIMES, VPos.BASELINE, true)
        );
        gridPane.getColumnConstraints().addAll(
                new ColumnConstraints(10, 100, 500, Priority.SOMETIMES, HPos.CENTER, true),
                new ColumnConstraints(10, 100, 500, Priority.SOMETIMES, HPos.CENTER, true),
                new ColumnConstraints(10, 100, 500, Priority.SOMETIMES, HPos.CENTER, true)
        );

        return gridPane;
    }

    private Node createDepositWithdrawalButtons() {
        HBox hBox = new HBox(15);
        amountTextField = new TextField();
        amountTextField.setPromptText("Number");
        amountTextField.setPrefWidth(75);

        hBox.getChildren().addAll(withdrawalButton, amountTextField, depositButton);
        return hBox;
    }

    public Parent getView() {
        return view;
    }
}

The Controller

Now, we’ll need to modify the Controller to take a MainView object, and make the same connections as before – linking the Model, Controller and View objects for functionality.

package com.edencoding.controllers;

import com.edencoding.models.FinancialAccount;
import com.edencoding.views.MainView;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;

public class MainViewControllerNoFXML {

    //Model
    FinancialAccount account;

    public MainViewControllerNoFXML(MainView view){
        setView(view);
    }

    public void setView(MainView view){
        //get model
        account = new FinancialAccount("Maxwell Planck", 6626, 1000d);

        //link Model with View
        view.getAccountHolderDetailsLabel().textProperty().bind(account.accountHolderProperty());
        view.getAccountBalanceDetailsLabel().textProperty().bind(account.accountBalanceProperty().asString());
        view.getAccountNumberDetailsLabel().textProperty().bind(account.accountNumberProperty().asString());

        //link Controller to View - ensure only numeric input (integers) in text field
        view.getAmountTextField().setTextFormatter(new TextFormatter<>(change -> {
            if (change.getText().matches("\\d+") || change.getText().equals("")) {
                return change;
            } else {
                change.setText("");
                change.setRange(
                        change.getRangeStart(),
                        change.getRangeStart()
                );
                return change;
            }
        }));

        //link Controller to View - methods for buttons
        view.getDepositButton().setOnAction(event -> {
            account.deposit(getAmount(view.getAmountTextField()));
            event.consume();
        });
        view.getWithdrawalButton().setOnAction(event -> {
            account.withdraw(getAmount(view.getAmountTextField()));
            event.consume();
        });
    }

    private double getAmount(TextField field){
        if (field.getText().equals("")) return 0;

        return Double.parseDouble(field.getText());
    }
}

Launching the application

Finally, we need to launch the app, loading the View and Controller up inside the start() method of our application, and loading the View in to the window.

package com.edencoding;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class MVCExampleApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("/fxml/mainView.fxml"));
        primaryStage.setTitle("MVC Example App");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

And that’s it! As I say, I don’t think t’s as clean, but it definitely works, and does avoid the need to create Views in FXML.

Full code:

If you want the full code, you can get it all in my GitHub here.

MVC Best Practices in JavaFX

In general, sticking to concepts like the Single Responsibility Principle and keeping your code modular and concise will help in generally keeping your code understandable and useable.

Here are a few pieces of advice I usually have to remind myself to do whenever I’m applying MVC in JavaFX!

Model

If you have multiple types of data – even separate classes, you should aggregate them all into a single UI-specific Model that you link with your Controller.

A Model should not be a loose collection of classes, all wired into the Controller and View separately.

The model should be one coherent class, with all of the information you’ll need to display to the user.

A simple MVC app structured with a single model

When the complexity of a program increases, the temptation is just to wire in a little extra data as a separate class, figuring the Controller can still see it, so what’s the problem?

The problem is that the number of these connections tend to proliferate over time until the Controller is acting as the Model, aggregating all of the data together. This breaks the MVC pattern and means you can’t reuse your model in different windows.

Randomly wiring multiple models into views is a bad idea

A better system is to aggregate your data into a single Model that defines the business logic needed for the application.

The Model of an MVC pattern should be one consistent object that can organise all of the data for the View

This way, when you need a new View, you have a single Model you can connect.

View

The view should be a visual representation of what’s relevant to the user. It should have one simple and consistent narrative for one type of interaction.

A View does not need to display all of the available information in the Model

A View should present information that is relevant to the user’s requirements at that point in the user experience journey. Displaying too much in your View can lead to a bad user experience.

The view does not need to display everything to the user at once

If the user is a manager, they may not need to see an individual breakdown of line-by-line detail. If they’re QA, they may only need an approval status summary.

Separate Views should be created for different user requirements

Remember: Models can be used to feed multiple View objects, but Views don’t have to show you everything.

A View should not try to handle every potential user interaction or editing.

One temptation when you’re defining a View is to try and deal with everything a user might want to do in a single View.

An Editable TableView is a great example of this. For the first 5 years of working with JavaFX, every time I worked with a TableView, I’d try and implement some sort of editable interface that allowed live editing of values in the Table.

It’s really hard work.

So, unless you have a lot of time to spend creating custom functionality, work within the constraints of JavaFX to create multiple Views that allow data display and editing without breaking your own back.

Controller

The Controller defines how your application should behave. Ultimately it can do this by making changes to either the Model or the View. However:

The Controller should not update information in the View directly.

JavaFX is not designed for this. It should update the Model, which will in turn update the View through its property’s bindings.

Multiple Controllers in an application should not need to know about each other.

When you have multiple windows it’s really tempting to manipulate your Views through updates passed from Controller to Controller.

Please believe me when I say this might look good to start with, but can lead to some nightmare code that’s very difficult to maintain.

Controllers should not update each other. This s bad practice

Controllers operating on the same data should perform updates through the Model.

Separated controllers should operate through a Model

This even extends to important things like the lifecycle of an application. If you find yourself in a situation where you need a “Master” Controller to maintain the lifecycle of an application, I’d suggest moving to a model where the Lifecycle is recorded in the Model, which can be updated from multiple Controllers.

Conclusions

It’s surprisingly easy to create an MVC app in JavaFX. In fact, the JavaFX structure is fundamentally set up to follow this pattern. You can create an MVC app both with FXML and without it, although I favour FXML because it lends itself towards looser coupling.

Each of the Model, View and Controller objects should strictly follow the Single Responsibility Principle. By doing that, you avoid tight coupling, and maximise your chances of creating code that’s easier to maintain.

Finally, if you find yourself looking to communicate between controllers, it may be a good idea to think about storing that information in the Model and using that to avoid ControllerController coupling.