Simple Date Processing in Java with LocalDate

Calendar icon
Image courtesy of https://www.stockio.com/free-icon/karlton-icon-set-calendar

I’m working on a project in Java, and I wanted to do some simple processing of dates.  I tried reading the Java tutorials and looking at questions and answers on Stack Overflow, but the solutions they offered were too complicated for what I was looking for, or were using classes that were old and not recommended to be used.  I finally figured out a nice easy way to handle the processing, so I thought I’d document it here.

What I was trying to do

The dates I have are stored in Strings, in the format “YYYY-MM-DD”; for example, June 30, 2019 is stored as “2019-06-30”.  This is a nice easy format to work with; I don’t need to concern myself with timezones or anything complicated, and it’s easy to sort and compare, because I can just do String comparisons with the compareTo() method.

Besides comparisons and sorting, I also want to do some other processing: getting the date the day before, the week before, and the week after.  This isn’t so easy to do; I would need to write a bunch of code that knows how many days there are in the month, and handle the case of moving to previous months or years, and deal with leap years, etc., etc., etc.  Yeah, I know it’s not that hard, but it would be easier to use some pre-existing, exhaustively tested code to do it!

Java’s LocalDate class is the solution

It turns out the best way to do this is to use Java’s LocalDate class, in the java.time package.  A LocalDate object contains a date without a timezone, using the standard Gregorian calendar that we all know and love in Canada (and most of the world).  If you really want to know more details about the calendar (you don’t need to to understand this blog post), you can read about the ISO 8601 calendar on Wikipedia.

Creating a LocalDate object

Creating a LocalDate object was really easy for me.  Because I’ve already got the date stored in my program in a String in the format YYYY-MM-DD, I can just call the parse() method; I pass in the String, and it returns a LocalDate object:

LocalDate someDate = LocalDate.parse( "2019-06-30" );

There are other lots of other methods you can use to create LocalDate objects, but this was all I needed to do.  Take a look at the API for other possible methods, like now() or of().

Converting back to a String

LocalDate provides a toString() method, which returns a String in the YYYY-MM-DD format:

String dateAsString = someDate.toString();

Of course, because Java will call toString() automatically when converting an object to a String, I can display the date with:

System.out.println( "The date is " + someDate );

LocalDate manipulation

If I want to get the day before the current date, I can use the minusDays() method:

LocalDate dayBefore = someDate.minusDays( 1 );

Note that LocalDate objects are immutable; once they’re set, you can’t change them, so the minusDays() method doesn’t change the original date, it returns a new LocalDate object with the new date.  As a result, if I wanted to change the someDate variable, I’d have to do:

someDate = someDate.minusDays( 1 );

If I want to change by other units, there’s also minusWeeks(), minusMonths(), and minusYears().  You can also use the minus() methods for more complicated manipulation, but I didn’t need to bother with that.  So anyways, here’s how I can get the date from the week before:

LocalDate weekBefore = someDate.minusWeeks( 1 );

Similarly, if I want to get the next day, I can use the plusDays() method:

LocalDate dayAfter = someDate.plusDays( 1 );

There are similar methods for plusWeeks(), plusMonths(), plusYears(), and plus().

Comparing LocalDates

LocalDate provides the standard comparison functions that we’re all used to: the equals() method checks if two dates are equal:

someDate.equals( dayBefore )

evaluates to false, and the compareTo() method compares two dates, returning an integer less than, equal to, or greater than zero:

someDate.compareTo( dayBefore )

evaluates to a value greater than 0.

For some reason, my brain has never been able to remember the order of compareTo(); does it compare the parameter to the object, or the object to the parameter?  I always have to try it out in test code, and I inevitably get it the wrong way! But, with LocalDates, there’s some nice comparison methods I can use so I don’t have to think about it!  If I want to see if a date is before, I can use the isBefore() method:

someDate.isBefore( dayBefore )

evaluates to false.  I can wrap my head around that: I just read the code and it makes sense — “is someDate before dayBefore?”

There’s also an isAfter() method:

someDate.isAfter( dayBefore )

evaluates to true.

There’s also an isEqual() method; you can use that, or equals().

I wish there was an isOnOrAfter() or isOnOrBefore() method, but there’s not; I just have to use compareTo() for that.

Other methods

LocalDate provides lots and lots of other methods, but I didn’t need to use them.  Take a look; if you’re wanting to do some simple date manipulation, there might be something there you want to use!

All the code

Here’s the program I used to test all the code in this post:

import java.time.LocalDate;

public class SimpleDateExample {
    public static void main( String[] args ) {
        LocalDate someDate = LocalDate.parse( "2019-06-30" );
        String dateAsString = someDate.toString();
        System.out.println( "dateAsString = " + dateAsString );
        System.out.println( "The date is " + someDate );

        LocalDate dayBefore = someDate.minusDays( 1 );
        System.out.println( "Day before: " + dayBefore );

        // someDate = someDate.minusDays( 1 );
        // System.out.println( "The date is " + someDate );

        LocalDate weekBefore = someDate.minusWeeks( 1 );
        System.out.println( "Week before: " + weekBefore );

        LocalDate dayAfter = someDate.plusDays( 1 );
        System.out.println( "Day after: " + dayAfter );

        System.out.println( "Using equals: " +
            someDate.equals( dayBefore ) );

        System.out.println( "Using compareTo with dayBefore: " +
            someDate.compareTo( dayBefore ) );

        System.out.println( "Using compareTo with dayAfter: " +
            someDate.compareTo( dayAfter ) );

        System.out.println( "Using isBefore: " +
            someDate.isBefore( dayBefore ) );

        System.out.println( "Using isAfter: " +
            someDate.isAfter( dayBefore ) );
    }
}

How to set up SQLite with JDBC in Eclipse on Windows

SQLite bannerI’m working on a little project written in Java, and I want to have a simple database in it.  I decided the easiest way to do it was with SQLite, but it’s been quite a while since I used SQLite in Java.  So, in this post, I’m recording steps on what I had to do to use SQLite with JDBC in Eclipse on Windows, and I also have some really simple sample code showing how to use it.

What’s SQLite?

According to the SQLite home page:

SQLite is a C-language library that implements a small, fast, self-contained, high-reliability, full-featured, SQL database engine. SQLite is the most used database engine in the world. SQLite is built into all mobile phones and most computers and comes bundled inside countless other applications that people use every day.

OK, good enough for me.

Wait a minute, SQLite is a C language library — I want to use it in Java!

Luckily, somebody has created a JDBC driver wrapped around the C language library, so that we can use it in Java programs.  The code for the JDBC driver is available on GitHub at https://github.com/xerial/sqlite-jdbc, and there’s extensive documentation linked from that page.

Downloading the JDBC driver

You can get the JDBC driver from a page on BitBucket: https://bitbucket.org/xerial/sqlite-jdbc/downloads/.  At the time that I wrote this post, the latest version was 3.30.1 — download the file named sqlite-jdbc-3.30.1.jar, or a more recent version if there’s one available when you read this.

Installing the JDBC driver into an Eclipse project

OK, I’m going to create a new Eclipse Java project to contain some examples of using the JDBC driver, and then I’ll install the JDBC driver into that.

From the Eclipse menu, select File, then under that, select New, and then Java Project.  This is the same way you create any Java project; using the JDBC driver makes no difference in this step.  Give your project a name (I called mine jdbc-example), and select whatever options you want. I pretty much left mine at the default:

Create a Java Project window

Click Finish (or Next if you want to do additional configuration), and you’ll have a new empty Java project.  Here’s my project in the Eclipse Package Explorer:

New empty project in Package Explorer

Now, you’re going to want to put the JDBC driver that you downloaded somewhere that you can reference it.  To make things easy, I’m just going to put it in my project folder. Copy the Jar file into the top level of your project — I just used the File Explorer to drag it from my Downloads folder and dropped it on the jdbc-example folder in the Eclipse Package Explorer.  When I did that, Eclipse gave me the following window:

Drag-and-drop JAR file

I want to make a copy of the Jar file, rather than linking to the file in my Downloads folder, so I left “Copy files” selected and pressed OK.  When I did that, I can see that a copy of the Jar file is now in my project:

Package Explorer with Jar file

Note that I could put it anywhere; I can even reference it in my Downloads folder, but I like to keep my project files all together.  Anyways, the next step is to tell Eclipse that I want to add that Jar file to my project’s Java Build Path. Open the Project Properties by right-clicking on the jdbc-example project in the Package Explorer, then selecting Properties at the bottom of the pop-up menu (or alternatively, just click once on the jdbc-example project, then press Alt-Enter).  You’ll now get the project’s Properties window, and it will look something like this (but without the red ellipse!):

Initial project properties window

I want to change the Java Build Path.  Click on Java Build Path on the left-hand side of the window (circled in red), and your window will now look like something like this (again, without the red ellipse):

Properties window: Java Build Path

I want to change the Libraries, so on the right side of the window, select the Libraries pane (circled in red).  Then, you’ll get:

Java build path - Libraries pane

As you can probably guess, you’ll now want to click on the “Add JARs…” button.  You’ll get the following pop-up window, listing the projects in your Eclipse workspace:

JAR selection - collapsed

Expand your project:

JAR selection - expanded

Select sqlite-jdbc-3.30.1.jar, and press OK.  You’ll now see that file show up in the Libraries pane (funky hand-drawn arrows added by me!):

Jar added to build path

And you’re now good to go!  Click the “Apply and Close” button, and you’re all set to use JDBC in your Java project in Eclipse!

Is there any SQLite-JDBC API documentation?

Yup, there sure is.  I found it at https://www.javadoc.io/doc/org.xerial/sqlite-jdbc/3.30.1/index.html.

Some sample code

OK, you’re probably thinking, that’s great, but how do I use it?  I’ve got some sample code and explanations below, but you can also check out the examples on the JDBC driver’s GitHub page and the official “JDBC Basics” Java Tutorial from Oracle.

Creating a database

The first thing you’re going to want to do is create the database.  You might already have one that you’re trying to access, but if you don’t, here’s some code to do it for you.

Actually, all we’re really going to do is to try to connect to a database.  If it doesn’t already exist, the SQLite JDBC driver will create one for us.

In JDBC, a database is represented by a DataSource, so the first thing we need to do is to create a DataSource.  Well, actually, a DataSource is just an interface, so we need to create an object that implements that interface.  The driver supplies a class called SQLiteDataSource that we’ll use. Note that to use it, you’ll need to import it from the org.sqlite.SQLiteDataSource package; you should be able to get Eclipse to do that for you when it gives you an error about not being able to resolve SQLiteDataSource to a type.  Anyways, here’s some code that creates the object:

SQLiteDataSource ds = new SQLiteDataSource();

We’ve now got a DataSource object, but it doesn’t know where the database is!  We can tell it that by using the setUrl method:

ds.setUrl("jdbc:sqlite:test.db");

So, this tells the driver we’re going to connect to an SQLite database in a file called test.db in the current directory.  If you want to specify a complete pathname, you’d do something like:

ds.setUrl("jdbc:sqlite:C:/users/shane/mydatabase.db");

Note that you’d use forward slashes, not backslashes as you might expect since we’re doing this on Windows!

Once we’ve got the DataSource all set up, we need to tell it to create the database.  Actually, what we’re going to do is try to connect to the database; if it doesn’t exist, the JDBC driver will create it for us.  So, we need to create a Connection object. We can do that with:

Connection conn = ds.getConnection();

And that’s it!

OK, I used a bit more code.  Because setting up a DataSource might throw an exception, I want to wrap that in a try-catch block.  And because a Connection can throw an SQLException when I try to get the connection, I’ll do something similar.  Note, though, that a Connection implements the AutoCloseable interface, so I can use the try-with-resources statement to make my code a little cleaner.  So, here’s some example code that will create an SQLite database in a file called test.db:

import java.sql.Connection;
import java.sql.SQLException;

import org.sqlite.SQLiteDataSource;

public class CreateDbExample {

    public static void main(String[] args) {
        SQLiteDataSource ds = null;

        try {
            ds = new SQLiteDataSource();
            ds.setUrl("jdbc:sqlite:test.db");
        } catch ( Exception e ) {
            e.printStackTrace();
            System.exit(0);
        }
        System.out.println( "Opened database successfully" );

        try ( Connection conn = ds.getConnection() ) {
        } catch ( SQLException e ) {
            e.printStackTrace();
            System.exit( 0 );
        }

        System.out.println( "Created database successfully" );
    }

}

A plea: this exception-catching code is horrendous.  Although it’s fine for an example, don’t do it this way in real code.  Think about what you’re doing, and properly handle and clean up an exception!

When you run this program, a file named test.db will be created in your project folder.  If you refresh Eclipse’s Package Explorer, you’ll see it show up:

Package explorer with DB file

Creating a database with a DriverManager

The old way of using JDBC was to use a DriverManager to create the connection.  The code is simpler, but Oracle recommends using a DataSource for creating connections.  But, if you want to do it the old way, here’s sample code:

import java.sql.Connection;
import java.sql.DriverManager;

public class CreateDbWithDriverManagerExample {

    public static void main(String[] args) {
        try (Connection conn =
                DriverManager.getConnection("jdbc:sqlite:test.db");
            ) {
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(0);
        }

        System.out.println("Opened database successfully");
    }

}

Creating a table

Creating a table is a nice example, because you will use the same concepts to perform many other database operations, other than reading from the database.  Anyways, you start by creating a DataSource object and a Connection object. Once you’ve got a Connection object, you can create a Statement object, and you’ll use that Statement object to execute the command.  To create the Statement object from a Connection object named conn, do the following:

Statement stmt = conn.createStatement();

Again, a Statement implements AutoCloseable, so you can use a try-with-resources statement to simplify your code (see the full example code below).

Once you’ve got a Statement, there are three methods that you can call to execute database commands:

  • executeUpdate(), which is used to update the database.  It returns the number of rows in the database that were affected by the Statement.  This is what you’ll use when you execute a CREATE, INSERT, DELETE, or UPDATE statement.
  • executeQuery(), which is used when you are making a query against the database.  It returns a ResultSet object, which contains a number of results. This is what you’ll use when you execute a SELECT statement.
  • execute(), which is used when you are expecting one or more ResultSet objects to be returned.  I won’t be using this in this post, because I’ll only return a single ResultSet object — ResultSet objects can contain more than one result.

OK, so if we’re going to create a table, we’ll want to use executeUpdate.  Here’s the SQL code to create a table I’ll use in these examples:

CREATE TABLE IF NOT EXISTS test
  ( ID INTEGER PRIMARY KEY, 
    NAME TEXT NOT NULL );

To run the command, pass it into executeUpdate() as a String.  For my example code, I’m going to create a String object containing the command, then pass that String in.  I could just pass a String constant in, but I thought my code was a little more readable this way.

String query = "CREATE TABLE IF NOT EXISTS test ( " +
                 "ID INTEGER PRIMARY KEY, " +
                 "NAME TEXT NOT NULL )";

Statement stmt = conn.createStatement();
int rv = stmt.executeUpdate( query );

Note: rv is short for return value.  Here’s my full code example:

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

import org.sqlite.SQLiteDataSource;

public class CreateTableExample {

    public static void main(String[] args) {
        SQLiteDataSource ds = null;

        try {
            ds = new SQLiteDataSource();
            ds.setUrl("jdbc:sqlite:test.db");
        } catch ( Exception e ) {
            e.printStackTrace();
            System.exit(0);
        }
        System.out.println( "Opened database successfully" );

        String query = "CREATE TABLE IF NOT EXISTS test ( " +
                         "ID INTEGER PRIMARY KEY, " +
                         "NAME TEXT NOT NULL )";

        try ( Connection conn = ds.getConnection();
              Statement stmt = conn.createStatement(); ) {
            int rv = stmt.executeUpdate( query );
            System.out.println( "executeUpdate() returned " + rv );
        } catch ( SQLException e ) {
            e.printStackTrace();
            System.exit( 0 );
        }
        System.out.println( "Created database successfully" );
    }

}

When you run it, it creates the table.  Note that rv will have the value 0, because no rows were updated.

Inserting rows into the table

Inserting rows into the table is basically the exact same code, you just change the query String:

String query = "INSERT INTO test ( NAME ) VALUES ( 'Shane' )";

When you call executeUpdate(), it will return the value 1, because you have updated 1 row.

Here’s a snippet of my example code.  I’m actually inserting two rows, one at a time.  In my example, you can see they are both wrapped inside the same try-with-resources statement:

System.out.println( "Attempting to insert two rows into test table" );

String query1 = "INSERT INTO test ( NAME ) VALUES ( 'Shane' )";
String query2 = "INSERT INTO test ( NAME ) VALUES ( 'Sharon' )";

try ( Connection conn = ds.getConnection();
      Statement stmt = conn.createStatement(); ) {
    int rv = stmt.executeUpdate( query1 );
    System.out.println( "1st executeUpdate() returned " + rv );

    rv = stmt.executeUpdate( query2 );
    System.out.println( "2nd executeUpdate() returned " + rv );
} catch ( SQLException e ) {
    e.printStackTrace();
    System.exit( 0 );
}

Updating and deleting rows

Once again, when updating and deleting rows, you use the same code, just change the query string.  Examples:

String query = "UPDATE test SET NAME = 'Poopsie'" +
               " WHERE NAME = 'Sharon'";

and

String query = "DELETE FROM test WHERE NAME = 'Shane'";

Deleting a table

Deleting a table works the same way, just a different query String:

String query = "DROP TABLE IF EXISTS test";

Querying a table

To query a table, create a query String, but pass it into executeQuery() rather than executeUpdate():

String query = "SELECT * FROM test";

ResultSet rs = stmt.executeQuery(query);

A ResultSet object is returned.  This contains a number of results.  See the Java Tutorial “Retrieving and Modifying Values from Result Setsfor details, but here’s a simple case where I loop through all the results that come back in the ResultSet and display them:

while ( rs.next() ) {
    int id = rs.getInt( "ID" );
    String name = rs.getString( "NAME" );

    System.out.println( "Result: ID = " + id + ", NAME = " + name );
}

The next() method returns true if there are more results in the ResultSet to be processed.  To get the actual values from the result, you use the various get*() methods, depending on the type of the row.  In my example table, the ID field is an INTEGER, and the NAME field is TEXT, so I use getInt() for the ID and getString() for the NAME.

Here’s my full example code:

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.sqlite.SQLiteDataSource;

public class SelectRowsExample {

    public static void main(String[] args) {
        SQLiteDataSource ds = null;

        try {
            ds = new SQLiteDataSource();
            ds.setUrl("jdbc:sqlite:test.db");
        } catch ( Exception e ) {
            e.printStackTrace();
            System.exit(0);
        }

        System.out.println( "Opened database successfully" );

        System.out.println( "Selecting all rows from test table" );
        String query = "SELECT * FROM test";

        try ( Connection conn = ds.getConnection();
              Statement stmt = conn.createStatement(); ) {
            ResultSet rs = stmt.executeQuery(query);

            while ( rs.next() ) {
                int id = rs.getInt( "ID" );
                String name = rs.getString( "NAME" );

                System.out.println( "Result: ID = " + id +
                    ", NAME = " + name );
            }
        } catch ( SQLException e ) {
            e.printStackTrace();
            System.exit( 0 );
        }
    }

}

Final words

So, there’s the basics of how you set up the SQLite JDBC driver, and some simple examples of using it.  Refer to the Oracle documentation for more detailed examples, but these examples should get you through the basics.  Have fun!

Where can I get a copy of the sample code?

I’m glad you asked.  I put a copy up on BitBucket.  You can find it at https://bitbucket.org/shaneroo/jdbc-example.

Summary: Setting up a Debian Vagrant box on Windows 10 with VirtualBox

File was modified as follows: converted from SVG to JPG
Debian-OpenLogo” by Ebrahim is licensed under CC BY-SA 3.0

In my three previous posts (Post 1, Post 2, Post 3), I detailed the steps involved in setting up a Debian Vagrant box on Windows 10.  It contains lots of explanations and screenshots; if you know what you’re doing, and you just want to see the steps, this is the post for you!

Install the vagrant-vbguest plugin, if you don’t have it installed already:

vagrant plugin install vagrant-vbguest

Create the Vagrantfile:

vagrant init debian/buster64

Update the Vagrantfile by adding the following lines to the appropriate locations:

config.vm.synced_folder ".", "/vagrant", type: "virtualbox"

config.vm.provider "virtualbox" do |vb|
  vb.gui = true
  vb.memory = "2048"
  vb.customize ["modifyvm", :id, "--vram", "12"]
end

Start up the VM.  This will download the Vagrant box if required, then create the VM, start it up, and install the VirtualBox Guest Additions:

vagrant up

SSH in to the VM, update the software, and install the GUI software:

vagrant ssh
sudo apt-get update
sudo apt-get dist-upgrade
sudo apt-get autoremove
sudo tasksel install xfce-desktop

Reboot the system, and maximize the GUI window after the system has booted:

logout
vagrant halt
vagrant up

Log in to the GUI (username vagrant, password vagrant).

And, you’re done!

Adding a GUI to a Debian Vagrant box

File was modified as follows: converted from SVG to JPG
Debian-OpenLogo” by Ebrahim is licensed under CC BY-SA 3.0

This is the third post in a series explaining how to set up a Debian Vagrant box on a Windows 10 machine.  In the first post, I talk about how to download a Debian base box from the Hashicorp atlas repository of base boxes, and how to create a Virtual Machine from that base box.  In the second post, I describe how to install the VirtualBox Guest Additions into your Debian box, and how to configure a shared folder between the VM and your Windows host machine.  In this post, I describe how to install a GUI.

If you know what you’re doing, and you want to skip all the explanations and just see the steps involved, feel free to look at my summary!

Note: this blog post was written with Debian 9 Stretch, but I’ve tested it with 10 Buster, and everything works the same.  The screenshots will vary slightly, but everything works good!

Setting up your Vagrantfile

We’re going to make some configuration changes to our Vagrantfile, so if your VM is currently running, shut it down:

vagrant halt

The first thing we’re going to want to do is to set up our Vagrantfile to configure VirtualBox with our Vagrant box.  There are some minor changes we need if we’re going to run a GUI on our VM. Open up your Vagrantfile in your favourite text editor.  Find the section of commented out lines that begin with “Provider-specific configuration”:

Add the following lines after that section:

config.vm.provider "virtualbox" do |vb|
  vb.gui = true
  vb.memory = "2048"
  vb.customize ["modifyvm", :id, "--vram", "12"]
end

This part of my file now looks like:

What this does is configures VirtualBox.  If you are using a different provider, say VMware, you’ll need to do something different; that’s outside the scope of this post.

What these lines do is as follows:

  • “vb.gui = true”: configures VirtualBox to appear when it is run.  Up until now, when we start up our VM, we don’t actually see it. This line changes that.
  • “vb.memory = “2048””: assigns 2GB to your virtual machine.  This isn’t necessary for running a GUI, but the default of 512MB isn’t enough for the development work I do.  If you don’t have a lot of memory on your Windows host machine, leave this line out.
  • “vb.customize [“modifyvm”, :id, “–vram”, “12”]”: assigns 12MB of video RAM to the virtual video card.  The default is 8MB, and that’s not quite enough to run full-screen on my 1920×1080 display. The minimum is 9MB, but I like to give myself a bit of wiggle room.

Installing the GUI

Now that we’ve got the Vagrantfile set up, start up the VM:

vagrant up

The VM will start as normal, but what will be different is that you’ll see the VirtualBox GUI running.  We’ll just ignore it for now, and continue to ssh into our VM.

vagrant ssh

If it’s been a while since you set up your VM, you may want to make sure you’ve got all the latest packages, although this isn’t always necessary.  I do it for completeness. Execute these commands inside your SSH session:

sudo apt-get update
sudo apt-get upgrade

Now, there’s a single command that we need to execute to install the GUI.  I’m installing the XFCE desktop; it’s nice and simple and easy to use.

sudo tasksel install xfce-desktop

This command now goes out and downloads all the required packages, then installs them.  Be warned that this is a slow process; when I did this as I was writing this post, it required downloading 944 packages!  Also, know that if you want to wipe out your VM and recreate it with “vagrant destroy / vagrant up”, you’re going to have to download all these packages again.  But, sometimes that can’t be helped…

Once the download and install completes, log out of your SSH session, reboot the VM, and it will start up with the GUI!

logout
vagrant halt
vagrant up

Using the GUI

Once your VM boots, you’ll have a GUI window:


You can use this window as-is, but it’s kind of small.  I always maximize it. The first time you do this it’s going to look kind of strange:


Not a concern, as it will fix itself when we log in.  Now, log in on the GUI, using the username of vagrant and the password of vagrant.  The GUI will log you in, and resize to use the full screen:


And, you’re done!  You’ve now got a Debian box with a GUI running in Vagrant!

Shutting down the VM

When I’m done working with the GUI, I always first log out of the GUI (Applications / Log Out from the XFCE menu, then choose Log Out), then I switch to my Windows command prompt window and do a “vagrant halt”.  I don’t know if this is necessary, but it’s probably good practice: it lets the Debian GUI cleanly shut down, and then lets Vagrant cleanly shut down. It’s probably not a problem to just shut down the VM from the GUI, but I’m a little anal-retentive that way….

Installing VirtualBox Guest Additions in a Debian Vagrant box on Windows 10

Short answer: Use the vagrant-vbguest plugin to make things easy!

This is the second post in a series of three that describe how I set up my Debian development environment in Vagrant on Windows 10, using VirtualBox as the provider.  The first post shows how to set up a basic, command-line only, Debian box. The third describes how to install a GUI. In this post, I describe how to install the VirtualBox Guest Additions.

If you know what you’re doing, and you want to skip all the explanations and just see the steps involved, feel free to look at my summary!

Note: this blog post was written with Debian 9 Stretch, but I’ve tested it with 10 Buster, and everything works the same.  The screenshots will vary slightly, but everything works good!

What does the VirtualBox Guest Additions offer?

According to the VirtualBox User Manual, installing the Guest Additions provides the following advantages (read the User Manual for more details):

  • Mouse pointer integration: makes it easier to use the mouse between the virtual machine and your Windows host)
  • Shared folders: makes accessing files between your VM and your Windows host easier
  • Better video support: not a big deal if you’re doing command-line only work, but if you’re using a GUI, it makes a huge difference.  For me, I like to resize the GUI, and when I do that, the screen in Debian automatically resizes. Very nice.
  • Seamless windows: I’ve never actually used this.  Read the manual for what it is (as I should do!).
  • Generic host/guest communication channels: Something else I’ve never used….
  • Time synchronization: synchronizes the time between your VM and the Windows host.  If you ever pause your VM, this is really nice because it automatically updates the time in the VM when you unpause.  Not something you’ll ever notice when you’re using it, but you will notice if you don’t have this feature!
  • Shared clipboard: Easy cut-and-paste between your VM and the Windows host.
  • Automated logons (credentials passing): Never used this, so I can’t really comment.

What’s involved in installing the VirtualBox Guest Additions?

You’re probably convinced now that having the VirtualBox Guest Additions installed is a good thing.  Unfortunately, getting them installed is normally a bit of a pain. They are distributed within VirtualBox as an ISO file, which you can mount as a CD-ROM on your VM.  Then, you run the install script, and everything happens nice (hopefully): installing new device drivers, adding kernel modules, etc., etc. But, the problem I had, is that I need to have a bunch of software installed in my VM before I can install the Guest Additions.  I suppose that’s not that hard, I just need to figure out what I need first. I could even record the list of required software in a blog post!

Another problem is that every time you upgrade your version of VirtualBox, or the kernel in the virtual machine, you need to re-install the Guest Additions.  If you don’t, things likely still will work, but it’s better to be safe than sorry. VirtualBox will warn you if you’re running a different version of the Guest Additions, but you’ll need to update them yourself.

But, there’s a better way!

Using the vagrant-vbguest plugin

Vagrant allows you to extend its functionality using plugins.  There is a plugin, called vagrant-vbguest, that does all of the hard work for you involved in setting up the Guest Additions.  Once you’ve got it installed, every time you run vagrant, it will check to see if the Guest Additions are installed, and at the correct version; if not, it will automatically install them (or upgrade them) for you.  It will even automatically install the software required to do the installation!

I ALWAYS use the vagrant-vbguest plugin.  It’s possible to use plugins on a per-VM basis (I’ve read, anyways, but I’ve never tried it), but it makes things so much simpler, why would you not want to do it?

Installing the vagrant-vbguest plugin

Installing the vagrant-vbguest plugin is VERY easy.  Just execute:

vagrant plugin install vagrant-vbguest

This will go out to the Internet, download the plugin, and install it in Vagrant.


And that’s all there is to it!

Installing the VirtualBox Guest Additions with the vagrant-vbguest plugin

The wonderful thing about this plugin is, once it’s installed, you don’t have to do anything else!  Every time you start up a Vagrant box with “vagrant up”, the plugin will ensure that the Guest Additions are installed; if they’re not, it will install it for you.  Here we go:

vagrant up

And here’s some screenshots of the plugin in action.  First, the VM boots as usual:


Then, Vagrant checks to see if the Guest Additions are installed:


Next, it checks to see if the packages required to install the Guest Additions are installed in the VM; if they’re not, it installs them:


I’m not going to bother showing the installation of these 34 packages.  After they are installed, Vagrant mounts the Guest Additions ISO from VirtualBox into our VM:


Then, it actually installs the Guest Additions:


And finally, it starts up the Guest Additions, unmounts the ISO, and finishes the “vagrant up”:


Because we’ve made changes to the kernel, I always like to reboot the system after installing the Guest Additions.  Maybe it’s not necessary, but I prefer to be safe. Just shut it down and restart:

vagrant halt
vagrant up

You’ll now see, as part of the bootup sequence, that Vagrant checks to ensure that the Guest Additions are installed and running:

Setting up shared folders in your VM

Now that we’ve got the Guest Additions installed in the VM, let’s set up shared folders, so that we can easily access our Windows files in our VM, and vice-versa.  What we’ll do is set up a single folder that will be shared between the VM and the Windows host. First, shut down the VM:

vagrant halt

Now, use your favourite text editor to edit the “Vagrantfile” configuration file.  Within that file, you’ll find some lines like:

We’re going to add a new line after this to specify how we want to set up our shared folder.  What I’m going to do is have the current folder (the one with Vagrantfile in it) show up in my VM in the location /vagrant.  To do this, add the following line after the lines above:

config.vm.synced_folder ".", "/vagrant", type: "virtualbox"

This tells Vagrant to mount the current folder (“.”) on the Windows host into the VM at location “/vagrant”, and to use a VirtualBox shared folder.  There is also an “rsync” way of sharing folders, but that requires installing additional software on both Windows and the VM, and it doesn’t automatically sync the files.  I much prefer the VirtualBox shared folders. Anyways, that section of my Vagrantfile now looks like:

Start Vagrant back up:

vagrant up

You’ll notice in the bootup sequence a message indicating the shared folder:

Using the shared folders

Let’s actually try out the shared folders.  First, ssh into the VM:

vagrant ssh

If, from within the VM, you look at the contents of the /vagrant directory, you’ll see:


which is the same as what you see in File Explorer in Windows (not counting the hidden .vagrant folder!):


If you look at the contents of the Vagrantfile in the VM, you’ll see that it is the same as the file on Windows — they are the same file!

Next, try creating a file in the VM:


And you’ll see it’s also visible in Windows:


So, if you ever want to move files between the VM and the Windows host, just drop them in the appropriate folder, and you’re good to go!

Cleaning up

If you want to remove the plugin from Vagrant, it’s pretty easy to do:

vagrant plugin uninstall vagrant-vbguest


Note that this only uninstalls the plugin, it does not uninstall the VirtualBox Guest Additions.  If you do want to uninstall the Guest Additions, I’d suggest destroying the box, and recreating:

vagrant destroy
vagrant up

I personally prefer having the Guest Additions installed, so I always have the plugin installed.

What’s next?

In the first post, I looked at how you can set up a basic Debian box in Vagrant.  But, it’s command-line only. That’s OK for an old Unix hacker like me, but if you prefer a GUI, the next post talks about how to do that.

Setting up a Debian Vagrant box on Windows 10 with VirtualBox

File was modified as follows: converted from SVG to JPG
Debian-OpenLogo” by Ebrahim is licensed under CC BY-SA 3.0

Update on July 23, 2019: Since the time I originally made this post, Debian has moved from version 9.6 Stretch to 10.0 Buster.  I’ve updated the post accordingly.  Thankfully, nothing changed but the name of the Vagrant box to use!  If you still want to use Stretch, replace everywhere that it says stretch64 with buster64.  Some of your error messages may be different, though…

Lately, for my Linux development, I’ve been using Vagrant, using VirtualBox as a provider.  In Vagrant, I set up a Debian box and do my development on that. It’s nice and easy to set up, and if I want to restart with a clean installation of Debian, it’s very easy to do.

I’ll write three posts about how I set it up.  In this first post, I detail the steps that I use to create a basic Debian box, with no GUI.  In my second post, I describe the steps I use to set up VirtualBox’s Guest Additions, so that I can share files between my host Windows system and my Debian Vagrant box.  In the third post, I give the steps that I use to set up a GUI.

If you know what you’re doing, and you want to skip all the explanations and just see the steps involved, feel free to look at my summary!

Debian’s Vagrant Box

The Debian project provides standard Vagrant boxes in the Vagrant Cloud box catalog.  You can read about them on the Debian wiki, and look at the provided boxes on https://app.vagrantup.com/debian/.  The latest version of Debian is version 10, codenamed Buster.  At the time of writing this post, the latest point release was 10.0, and I’m using the 64-bit version of Debian.  So, the Vagrant box I’m using is debian/buster64.

The Debian Vagrant box provides a standard basic Debian system, containing all packages with priority required, important, and standard.  Note that it does NOT include a GUI; you’re just going to get a command-line version. We’ll look at how to set up a GUI in the third post.

Prerequisites

To set this up, you need to have both VirtualBox and Vagrant installed.  You can download VirtualBox from the website https://www.virtualbox.org.  The download page is https://www.virtualbox.org/wiki/Downloads, and the installation instructions are in Chapter 2 of the User Manual. You can download Vagrant from the website https://www.vagrantup.com, where the download page is at https://www.vagrantup.com/downloads.html, and the installation instructions are at https://www.vagrantup.com/intro/getting-started/install.html.  I installed VirtualBox first, then Vagrant.  I don’t know if the order matters, but it made sense to me, and I know that works!

Creating the Vagrantfile

Once you’ve got Vagrant and VirtualBox installed, you’re going to want to create the configuration file for your box.  This file is called Vagrantfile. I’ve created a folder under my home directory called vagrant-VMs, and underneath that, another folder called buster.  I’ve created multiple folders under vagrant-VMs, one for each Vagrant configuration, but in this post, I’m only discussing my Buster installation.

Here’s looking at my folder in File Explorer:

Open a command prompt, then change to the vagrant-VMs\buster folder:

cd vagrant-VMs\buster

Next, use Vagrant to create the Vagrantfile for us:

vagrant init debian/buster64

This will create the Vagrantfile that configures the usage of the Vagrant box.  The box won’t actually be downloaded until you try to bring the box up for the first time.

Here’s a screenshot.

You might want to take a look at the Vagrantfile; it’s just a text file (actually, a Ruby program file), so you can look at it in your favourite text editor, such as Notepad, Notepad++, or Vim.  There’s actually very little in the file; most of it is commented-out configuration, giving examples of things you may want to configure. But, for now, we’ll just leave things as they are.

Here’s a screenshot of the complete file (click to view it larger):

Creating the Virtual Machine

The next step is to create and start up the virtual machine.  We’ll use the “vagrant up” command to do that.  The first time you run this, it will connect to the Hashicorp atlas (a repository of Vagrant boxes, maintained by the developers of Vagrant) and download a copy of the box (called the “base box”).  Then, it will create a virtual machine as a copy of the downloaded base box; subsequent executions will just use the virtual machine you’ve already created.

Note: Vagrant creates a folder to hold all its configuration data under your home directory, with the name .vagrant.d.  If you’re curious where the downloaded base box goes, it goes underneath that folder, in a subfolder named boxes. On Windows, I use File History to back up my files; because these boxes are so big, and because I can easily re-download them, I don’t want to back them up, so I configure File History to not backup the .vagrant.d\boxes folder.  There’s another folder that I also don’t back up, but I’ll mention that next…

Second note: the virtual machine gets created in a folder in your home directory called “VirtualBox VMs”.  Once again, I don’t want to back these up, so I add this folder to my list of excluded folders in File History.  You may want to back them up, but as I said before, you can easily recreate these VMs, so it’s a waste of backup space to back them up.

OK, so let’s create our Virtual Machine:

vagrant up

You’ll see a whole bunch of messages.  Vagrant goes through the following steps when it creates the VM:

  • Imports the debian/buster64 base box and uses it to create a new VM
  • Configures the networking of the new VM
  • Boots the VM, and waits for the booting to complete
  • Uses SSH to connect to the VM, and replaces the SSH key in the VM (more on that later)
  • Checks to see if the VirtualBox guest additions are installed.

You’ll now see one of two messages about the VirtualBox guest additions.  If they are not installed in the Vagrant box (they shouldn’t be, but version 10.0.0 of the box seems to include them), you’ll get a scary error message telling you that you should install them, and that a lot of things won’t work until you install them.  Ignore that for now; I’ll look at installing the guest additions in my next post.  If they are installed (version 10.0.0, I’m looking at you!), you may get a slightly less scary message that the guest additions on the VM don’t match the installed version of VirtualBox.  Don’t worry about that, as we’ll install the proper version in the next post.

At the time of writing, I also get a VERY scary error message about attempting to rsync a shared folder.  Once again, don’t worry about that; we’ll resolve that in the next post as well.

With regards to SSH: the buster64 box that was downloaded from the Hashicorp atlas has a well-known SSH key, but you really want that to be secret.  But, if it was secret, we wouldn’t be able to connect to the VM. So, when we first bring up the VM, Vagrant connects to the VM using the well-known key, then generates a secret key, and replaces the well-known key with our new secret key.  Finally, it disconnects and reconnects using the new key, just to make sure everything is working OK.

Here’s a screenshot of all the output (click to enlarge):

You’ll notice that it doesn’t appear that the VM is running.  The “vagrant up” command completes and returns us to the command prompt, and there isn’t a VirtualBox GUI.  But, don’t worry, VirtualBox is running; you just can’t see it! If you really want, you can start up Windows’ Task Manager, and see the VBoxHeadless process listed under Background Processes.  Or, you can start up VirtualBox, and you’ll see that you’ve got a running VM!

Bringing Debian Up-to-date

There may be security patches issued by Debian that aren’t incorporated into your imported box.  The next thing we’re going to do is to bring all the software up-to-date.

Log in to the VM using the “vagrant ssh” command:

vagrant ssh

This will connect to the VM using SSH, and will log you in to an account named “vagrant”.  This account was present in the Vagrant box you downloaded; it’s a standard convention used by Vagrant boxes.  If you ever need to log in without using the “vagrant ssh” command, the password is also “vagrant”.

Here’s a screenshot:

Once you’re logged in, you’ll be at the Debian command prompt.  We’ll want to execute the following commands:

sudo apt-get update
sudo apt-get upgrade

The first command will connect to the Debian package repository, and download a list of all of the latest versions of Debian packages.  The second command will download and install any packages that have been updated in the repository since the Vagrant base box was created.

Here’s a screenshot of executing the update:

and another of executing the upgrade (click to enlarge).  Note that your packages being updated almost certainly will be different from mine.

Depending on which packages have been updated, there are two other possible commands you may need to execute.  If you get a message about some packages being held back, execute this command:

sudo apt-get dist-upgrade

And, if you get a message that some packages are no longer needed, you can remove them with:

sudo apt-get autoremove

Actually, there’s really no harm in executing both of these commands anyways, since we’re setting up a new VM; they just might not do anything.

And now, we’re done!  We’ve got an up-to-date Debian system running.

Log out of the SSH session to return to the Windows command prompt:

logout

Shutting down the Virtual Machine

The last thing to do is to shut down the virtual machine.  That’s very easy to do from the Windows command prompt. Execute:

vagrant halt

And you’ll get a message that Vagrant is gracefully shutting down the virtual machine.

Next time you want to start the VM back up, just go to your vagrant-VMs\buster folder (or whatever you called it), and re-execute “vagrant up”.  It won’t need to recreate the VM; it’ll just use it as it was the last time you used it.

Do you want to clean up?

For completeness, I should mention how you can clean up what you’ve done.

If you want to delete the VM that you created, execute:

vagrant destroy

That will delete the virtual machine.  You can easily recreate the VM, though, just be executing “vagrant up” again.  This is a nice easy way to start your VM over fresh if you mess it up somehow. It leaves your Vagrantfile intact, so any configuration changes you made in there will be used the next time you recreate the virtual machine.  But, any changes you made inside the VM (such as updating Debian!) will need to be redone.

If you also want to remove the base box, execute:

vagrant box remove debian/buster64

If you want to use it again, you’ll need to re-download it; executing “vagrant up” will automatically do that for you if you need it.

Next steps

It’s very useful to have the VirtualBox guest additions installed on your system; I detail how to do that in my next post.  This will allow you to share files between your VM and your Windows machine that the VM is running on. Also, if you’re running a GUI (which I’ll describe how to set up in my third post), it allows you to resize the window, which is very handy!