{"id":1170,"date":"2025-05-07T11:18:15","date_gmt":"2025-05-07T08:18:15","guid":{"rendered":"https:\/\/www.juustila.com\/antti\/?p=1170"},"modified":"2025-05-07T11:22:12","modified_gmt":"2025-05-07T08:22:12","slug":"managing-app-settings-with-java-properties","status":"publish","type":"post","link":"https:\/\/www.juustila.com\/antti\/2025\/05\/07\/managing-app-settings-with-java-properties\/","title":{"rendered":"Managing app settings with Java Properties"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Many (most?) apps need some kind of settings the user can modify, that influence on the behavior of the app across app launches. In this post, I&#8217;ll show how to implement this with <code>java.util.Properties<\/code> class in a Java \/ Swing game.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As an example, I will use the Snakeses game from the previous post. In this game, user can change the difficulty, game mode and light\/dark mode of the UI:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"680\" src=\"https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/Nayttokuva-2025-05-07-kello-9.38.33-1024x680.png\" alt=\"\" class=\"wp-image-1171\" srcset=\"https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/Nayttokuva-2025-05-07-kello-9.38.33-1024x680.png 1024w, https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/Nayttokuva-2025-05-07-kello-9.38.33-300x199.png 300w, https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/Nayttokuva-2025-05-07-kello-9.38.33-768x510.png 768w, https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/Nayttokuva-2025-05-07-kello-9.38.33-1536x1021.png 1536w, https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/Nayttokuva-2025-05-07-kello-9.38.33-1200x797.png 1200w, https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/Nayttokuva-2025-05-07-kello-9.38.33.png 1776w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The selections will be saved to and read from a file named <code>snakeses.ini<\/code>: <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#Snakeses Settings\n#Tue May 06 19:28:32 EEST 2025\ndifficulty=hard\nmode=dark\nplayername=Antti\nstyle=classic<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The settings file contains <em>key-value pairs<\/em> (e.g. on line <code>difficulty=hard<\/code> the  <code>difficulty<\/code> is a <em>key<\/em>, and <code>hard<\/code> is the <em>value<\/em>). Managing the settings is then just handling these key-value pairs, providing a user a way to change these in a controlled manner, and then handling the saving and restoring the values with the settings file. The file can also contain commented lines (beginning with <code>#<\/code>) that are just for humans to read, and are not actual settings.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As you can see, also the latest player&#8217;s name, who entered the Hall of Fame, is also stored in the settings. Assuming the game is mostly played by the same player, she does not need to write her name again and again, but can just accept the notification as you can see in the previous post video.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The settings are managed in a single class, unsurprisingly named <code>Settings<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public class Settings {\n    public Settings() {\n        difficulty = \"easy\";\n        style = \"modern\";\n        mode = \"dark\";\n        playerName = \"Anonymous\";\n    }\n\n    public String difficulty;\n    public String style;\n    public String mode;\n    public String playerName;\n\n    public boolean isDirty = false;\n    private static final String SETTINGS_FILE_NAME = \"snakeses.ini\";<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">As you can see, the constructor gives the default values to the various settings. You can also see that I&#8217;ve taken the easy road of letting the members be <code>public<\/code> instead of the default and recommended <code>private<\/code>. Lazy me, but I am myself making sure that as the only programmer in this project I will treat these public members responsibly and not mess the values with anything that is invalid. In a larger project I would let them be <code>private<\/code> and make sure that setter methods would check that the parameters to change the setting values would always be valid.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">How to read the settings from the <code>snakeses.ini<\/code> file, is shown here:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public void read() throws IOException {\n    File configFile = new File(SETTINGS_FILE_NAME);\n    Properties config = new Properties();\n    try (FileInputStream istream = new FileInputStream(configFile)) {\n        config.load(istream);\n        if (config.containsKey(\"difficulty\")) {\n            difficulty = config.getProperty(\"difficulty\");\n        } else {\n            difficulty = \"easy\";\n        }\n        if (config.containsKey(\"style\")) {\n            style = config.getProperty(\"style\");\n        } else {\n            style = \"modern\";\n        }\n        if (config.containsKey(\"mode\")) {\n            mode = config.getProperty(\"mode\");\n        } else {\n            mode = \"dark\";\n        }\n        if (config.containsKey(\"playername\")) {\n            playerName = config.getProperty(\"playername\");\n        }\n    } catch (FileNotFoundException e) {\n        difficulty = \"easy\";\n        style = \"modern\";\n        mode = \"dark\";\n        playerName = \"Anonymous\";\n        save();\n    } finally {\n        isDirty = false;\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Opening and reading the settings file is handled using <code>java.io.File<\/code>, <code>java.io.FileInputStream<\/code> and <code>java.util.Properties<\/code> classes. The <code>Properties<\/code> object will then contain the settings read from the ini file, after calling <code>config.load(istream)<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">After that, it is simple to read the setting values from the properties object by calling <code>config.getProperty<\/code>, giving the name of the property. Just to be sure, the code checks if the properties contain that key (using <code>config.containsKey<\/code>, and if it does not, a default value is used. This is necessary, since the file could be changed outside of the app, by the user, for example, so as a programmer you cannot trust that the file (after saving it) will surely contain those key-value pairs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Also, when the app launches for the first time, the settings file is not there. It could be delivered with the app with default values, but again, nothing stops the user accidentally or deliberately deleting that file. So the code must prepare for the situation that the settings file does not exist &#8212; that&#8217;s why the <code>catch (FileNotFoundException e)<\/code>. If the file is not there, again, resort to the default setting values. And <em>then<\/em> save the settings immediately, calling <code>save()<\/code>. That makes sure that at least now we have the settings file there.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The member variable <code>isDirty<\/code> is also set to false. <code>isDirty<\/code> is convenient to have. When the user opens up the settings panel and closes it, there is no sense in saving the settings if they were not changed. As you can see below, the <code>isDirty<\/code> is set to true if the settings change, and only then the settings are actually saved. No need to do any disk access if there is no need for it.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Do note that the app in this demo does not check if the settings are actually different from the original when user leaves the settings panel. If there are lots of settings and changing them leads to lots of code to be executed, you might want to keep the old settings somewhere, let the user to manipulate the settings, and then when the user is done, actually compare the old setting values to new ones. For those values that are actually different, then handle the changes to those settings only.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">How about saving the settings:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public void save() throws FileNotFoundException, IOException {\n    File configFile = new File(SETTINGS_FILE_NAME);\n    Properties config = new Properties();\n    try(FileOutputStream ostream = new FileOutputStream(configFile)) {\n        config.setProperty(\"difficulty\", difficulty);\n        config.setProperty(\"style\", style);\n        config.setProperty(\"mode\", mode);\n        config.setProperty(\"playername\", playerName);\n        config.store(ostream, \"Snakeses Settings\");\n    } finally {\n        isDirty = false;\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Saving settings to a file is handled using <code>java.io.File<\/code>, <code>java.io.FileOutputStream<\/code> and <code>java.util.Properties<\/code> classes. In this case we just set the various properties of the <code>Properties<\/code> object, using <code>config.setProperty<\/code>, giving the key and value pairs to the configuration, and finally call <code>config.store<\/code>. As you can see, the string &#8220;Snakeses Settings&#8221; and the date of the update is saved as comments to the <code>snakeses.ini<\/code> file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Also, the <code>isDirty<\/code> is set to false at this time; settings are now saved and not changed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">How the Settings panel then uses the settings when user is shown the current settings? As an example, let&#8217;s see how the game difficulty radio buttons are created and how the radio button corresponding to the current setting is selected, in <code>SettingsPanel<\/code> constructor, other details omitted:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public class SettingsPanel extends JPanel implements ActionListener {\n\n    private Settings settings;\n\n    public SettingsPanel(Settings settings) {\n        this.settings = settings;\n\n        ButtonGroup levelGroup = new ButtonGroup();\n        JRadioButton easy = new JRadioButton(\"Easy\");\n        easy.setActionCommand(\"easy\");\n        easy.addActionListener(this);\n        JRadioButton hard = new JRadioButton(\"Hard\");\n        hard.setActionCommand(\"hard\");\n        hard.addActionListener(this);\n        levelGroup.add(easy);\n        levelGroup.add(hard);\n        if (settings.difficulty.equals(\"easy\")) {\n            easy.setSelected(true);\n        } else {\n            hard.setSelected(true);\n        }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The &#8220;Easy&#8221; and &#8220;Hard&#8221; radio buttons are created to a <code>ButtonGroup<\/code>. That takes care of managing the principle of the related radiobuttos that only one of them can be selected at one time. The last if-else block shows how the current value of the <code>Settings<\/code> is used to select one or the other from these game difficulty radio buttons.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Did you know that the name &#8220;radio button&#8221; for this control comes from the actual radios to listen to radio stations? Radios used to have several buttons for selecting preselected radio station frequencies to listen to. Obviously, you can only listen to one station at a time. So pressing down one station button would then deselect (pop up) the previously selected station button.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s why these buttons are called &#8220;radio buttons&#8221;. Radio buttons in GUI frameworks are round to distinguish them from check boxes, which are rectangular and function differently (many of them can be selected simultaneously).<\/p>\n<\/blockquote>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"350\" height=\"263\" src=\"https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/9N0ZlOE8fXjirgEJeTGj7g-smallw.jpeg\" alt=\"A vintage radio device with white radio buttons and two round controls to change the volume and control the frequency.\" class=\"wp-image-1172\" srcset=\"https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/9N0ZlOE8fXjirgEJeTGj7g-smallw.jpeg 350w, https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/9N0ZlOE8fXjirgEJeTGj7g-smallw-300x225.jpeg 300w\" sizes=\"auto, (max-width: 350px) 85vw, 350px\" \/><figcaption class=\"wp-element-caption\">Row of seven white rectangular radio buttons in a vintage radio divide. Image from https:\/\/www.collectorsweekly.com\/stories\/273171-1950s-grundig-fleetwood-tube-radio-mode<\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">Obviously, the controls to use in managing the settings depends on the setting and the possible values of that setting. For example, if we would have a setting with multiple values, like e.g. 42 distinct values, using radio buttons would not be a good choice. Then one could use a dropdown \/ combobox list instead, populating the available options to the list and selecting the one found in the settings.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">What happens then when user changes the setting? In the code above, you can see that the <code>SettingsPanel<\/code> is set as the action listener to the radiobuttons, e.g. in <code>easy.addActionListener(this)<\/code>. Also, each radio button were given a <em>name<\/em> for the command they represent, e.g. like this: <code>easy.setActionCommand(\"easy\")<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The panel implements the <code>ActionListener<\/code> interface, and therefore must have the method <code>actionPerformed<\/code> overridden. Again, let&#8217;s look at the relevant parts of this method:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public void actionPerformed(ActionEvent e) {\n    final String action = e.getActionCommand();\n    if (action.equals(\"easy\") || action.equals(\"hard\")) {\n        if (!settings.difficulty.equals(action)) {\n            settings.difficulty = action;\n            settings.isDirty = true;\n        }\n    } else if (action.equals(\"classic\") || action.equals(\"modern\")) {\n\/\/ and the same for the other radio buttons...<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Here, we check which was the action command related to the action event. So if the command was &#8220;easy&#8221; or &#8220;hard&#8221;, we check if the related setting value is different, we then change it and also set the <code>isDirty<\/code> of the setting to <code>true<\/code> indicating a need to actually save the settings to the <code>snakeses.ini<\/code> file as seen above.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So far we have seen how the <code>Settings<\/code> class manages the settings file, and how the <code>SettingsPanel<\/code> displays the current settings, and then changes the settings based on user actions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">How the app then actually initializes the settings and saves them if they have been changed? This is done, in this app, in the <code>SnakesesApp<\/code> class that contains the <code>main<\/code> method of the app.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When the app is launched, the settings object is the first one to be created:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    public static void main( String&#91;] args )\n    {\n        javax.swing.SwingUtilities.invokeLater(new Runnable() {\n            public void run() {\n                new SnakesesApp().run();\n            }\n        });        \n    }\n    private static SnakesesGame game;\n    private static Settings settings;\n    private static HallOfFame hallOfFame;\n\n\/\/ Later...\n\nprivate void run() {\n    try {\n        settings = new Settings();\n        settings.read();\n        hallOfFame = new HallOfFame();\n        hallOfFame.read();\n        game = new SnakesesGame(GameViewConstants.GAME_WIDTH, GameViewConstants.GAME_HEIGHT, settings);\n        \/\/ GUI\n        JFrame mainFrame = new JFrame(\"Snakeses\");\n\/\/ And the rest of the GUI is then created...<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Things to note here: The app object contains the relevant application objects as member variables (game, settings, hall of fame). Also the GUI objects (frame, panels) are member variables, but left out from code snippet above since they are not relevant in this post.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As you can see, the settings are read from the settings file as the very first step of the application launch. Therefore, they are read from the file and available to the rest of the application immediately.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">How about saving the settings? This could have been done in the <code>SettingsPanel<\/code>, but in this implementation it is done also in the <code>SnakesesApp<\/code>. The reason is, that changing some of the settings needs to be reflected in other components of the app. The method <code>SnakesesApp.switchTo<\/code> is called by the <code>SettingsPanel<\/code> when the close button is pressed. The app then switches back to the game view. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But before that, the app first checks if the settings were changed (<code>isDirty<\/code> is true), and saves the settings. App also instructs the hall of fame object to switch the hall of fame visible to the user, to the corresponding game difficulty level hall of fame data. This is because the game always shows the hall of fame for the current difficulty level only. This is shown in the code snippet below:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public static void switchTo(final View view) {\n    CardLayout cardLayout = (CardLayout) (mainView.getLayout());\n    if (settings.isDirty) {\n        try {\n            settings.save();                \n            hallOfFame.setCurrentLevel(\n                HallOfFame.Level.fromString(settings.difficulty)\n            );\n\/\/ ...<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Other game objects and panels read the settings continuously as they do their things, so they need not to be updated the same way as the hall of fame needs to be. For example, the game panel that draws the snake and the food, uses the light\/dark mode setting in painting the graphics each time the game screen is drawn:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public void paintComponent(Graphics g) {\n\tsuper.paintComponent(g);\n\tif (settings.mode.equals(\"dark\") &amp;&amp; getBackground().equals(Color.WHITE)) {\n\t\tsetBackground(Color.BLACK);\n\t} else if (settings.mode.equals(\"light\") &amp;&amp; getBackground().equals(Color.BLACK)) {\n\t\tsetBackground(Color.WHITE);\n\t}\n\/\/ ....<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Summarizing, we have now seen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>how the game settings and the settings file can be managed using the Java <code>Property<\/code> class,<\/li>\n\n\n\n<li>how game settings panel can display and enable changing the settings,<\/li>\n\n\n\n<li>how the app initializes the settings at lauch and how it saves the settings if they are changed in the settings panel, <\/li>\n\n\n\n<li>how settings are used in the app when drawing the game screen, and<\/li>\n\n\n\n<li>how changed settings can be propagated, if necessary, to other game elements like the hall of fame.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Many (most?) apps need some kind of settings the user can modify, that influence on the behavior of the app across app launches. In this post, I&#8217;ll show how to implement this with java.util.Properties class in a Java \/ Swing game. As an example, I will use the Snakeses game from the previous post. In &hellip; <a href=\"https:\/\/www.juustila.com\/antti\/2025\/05\/07\/managing-app-settings-with-java-properties\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Managing app settings with Java Properties&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[2],"tags":[170,77,70,172,171,169,160,12],"class_list":["post-1170","post","type-post","status-publish","format-standard","hentry","category-coding","tag-configuration","tag-java","tag-programming","tag-properties","tag-radio-buttons","tag-settings","tag-swing","tag-teaching"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/posts\/1170","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/comments?post=1170"}],"version-history":[{"count":2,"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/posts\/1170\/revisions"}],"predecessor-version":[{"id":1175,"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/posts\/1170\/revisions\/1175"}],"wp:attachment":[{"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/media?parent=1170"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/categories?post=1170"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/tags?post=1170"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}